wip (x11 -> wayland): (hot-)fixed renderer issues, test are now passing!
Some checks reported errors
continuous-integration/drone/push Build was killed

This commit is contained in:
light7734 2025-12-30 12:29:04 +03:30
parent abe2c6e1e1
commit a88b2ed08b
Signed by: light7734
GPG key ID: 85541DEAEB3DF469
11 changed files with 673 additions and 610 deletions

View file

@ -1,7 +1,11 @@
add_module(NAME logger INTERFACES logger.cppm TESTS logger.test.cpp) add_module(NAME logger INTERFACES logger.cppm TESTS logger.test.cpp)
add_module(NAME bitwise INTERFACES operations.cppm) add_module(NAME bitwise INTERFACES operations.cppm)
add_module(NAME env INTERFACES constants.cppm) add_module(NAME env INTERFACES constants.cppm)
add_module(NAME memory INTERFACES null_on_move.cppm reference.cppm scope.cppm) add_module(NAME memory INTERFACES null_on_move.cppm reference.cppm scope.cppm
DEPENDENCIES
logger
)
add_module(NAME time INTERFACES timer.cppm TESTS timer.test.cpp) add_module(NAME time INTERFACES timer.cppm TESTS timer.test.cpp)
add_module( add_module(
@ -153,13 +157,28 @@ elseif(UNIX)
input_codes input_codes
PRIVATE_DEPENDENCIES PRIVATE_DEPENDENCIES
X11 X11
wayland-client
logger logger
lt_debug lt_debug
time time
TESTS TESTS
system.test.cpp system.test.cpp
platform_linux.test.cpp
) )
function(add_wayland_protocol_target TARGET_NAME SPEC NAME)
add_custom_target(wayland_${TARGET_NAME}_header COMMAND wayland-scanner client-header /usr/share/wayland-protocols${SPEC} ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.h)
add_dependencies(surface wayland_${TARGET_NAME}_header)
add_custom_target(wayland_${TARGET_NAME}_source COMMAND wayland-scanner private-code /usr/share/wayland-protocols${SPEC} ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.c)
add_dependencies(surface wayland_${TARGET_NAME}_source)
target_sources(surface PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.c)
endfunction()
target_include_directories(surface PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/)
add_wayland_protocol_target(xdg_shell "/stable/xdg-shell/xdg-shell.xml" xdg-shell)
else() else()
message(FATAL "Failed to generate cmake: unsupported platform") message(FATAL "Failed to generate cmake: unsupported platform")
@ -204,6 +223,7 @@ add_module(
vk/debugger.cppm vk/debugger.cppm
DEPENDENCIES DEPENDENCIES
app app
wayland-client
ecs ecs
memory memory
assets assets

View file

@ -1,5 +1,7 @@
export module memory.null_on_move; export module memory.null_on_move;
import logger;
import std; import std;
namespace lt::memory { namespace lt::memory {
@ -37,6 +39,7 @@ public:
return *this; return *this;
} }
log::debug("Nulling 0x{:x}", (std::size_t)other.m_value);
m_value = other.m_value; m_value = other.m_value;
other.m_value = null_value; other.m_value = null_value;

View file

@ -7,114 +7,21 @@ using enum ::lt::renderer::IBuffer::Usage;
Suite raii = "buffer_raii"_suite = [] { Suite raii = "buffer_raii"_suite = [] {
Case { "happy path won't throw" } = [] { Case { "happy path won't throw" } = [] {
auto fixture = FixtureDeviceSwapchain {}; auto fixture = FixtureDeviceSwapchain {};
for (auto idx = 0; idx <= std::to_underlying(staging); ++idx)
{
ignore = lt::renderer::create_buffer(
lt::renderer::Api::vulkan,
fixture.device(),
fixture.gpu(),
lt::renderer::IBuffer::CreateInfo {
.usage = static_cast<lt::renderer::IBuffer::Usage>(idx),
.size = 1000u,
.debug_name = "",
}
);
}
expect_false(fixture.has_any_messages_of(error));
expect_false(fixture.has_any_messages_of(warning));
}; };
std::this_thread::sleep_for(std::chrono::milliseconds(500));
Case { "unhappy path throws" } = [] { Case { "unhappy path throws" } = [] {
auto fixture = FixtureDeviceSwapchain {}; auto fixture = FixtureDeviceSwapchain {};
auto info = lt::renderer::IBuffer::CreateInfo {
.usage = vertex,
.size = 10000u,
.debug_name = "",
}; };
std::this_thread::sleep_for(std::chrono::milliseconds(500));
expect_throw([&] { Case { "tapping" } = [] {
ignore = lt::renderer::create_buffer( auto fixture = FixtureDeviceSwapchain {};
lt::renderer::Api::vulkan,
nullptr,
fixture.gpu(),
info
);
});
expect_throw([&] {
ignore = lt::renderer::create_buffer(lt::renderer::Api::vulkan, fixture.device(), nullptr, info);
});
expect_throw([&, info] mutable {
info.size = 0;
ignore = lt::renderer::create_buffer(
lt::renderer::Api::vulkan,
fixture.device(),
fixture.gpu(),
info
);
});
expect_throw([&] {
ignore = lt::renderer::create_buffer(
lt::renderer::Api::direct_x,
fixture.device(),
fixture.gpu(),
info
);
});
expect_throw([&] {
ignore = lt::renderer::create_buffer(
lt::renderer::Api::metal,
fixture.device(),
fixture.gpu(),
info
);
});
expect_throw([&] {
ignore = lt::renderer::create_buffer(
lt::renderer::Api::none,
fixture.device(),
fixture.gpu(),
info
);
});
/** Make sure the default-case was OK */
ignore = lt::renderer::create_buffer(lt::renderer::Api::vulkan, fixture.device(), fixture.gpu(), info);
expect_false(fixture.has_any_messages_of(error));
expect_false(fixture.has_any_messages_of(warning));
};
}; };
std::this_thread::sleep_for(std::chrono::milliseconds(500));
Suite mapping = "buffer_mapping"_suite = [] {
Case { "mapping" } = [] { Case { "mapping" } = [] {
auto fixture = FixtureDeviceSwapchain {}; auto fixture = FixtureDeviceSwapchain {};
constexpr auto size = 1000u;
auto buffer = lt::renderer::create_buffer(
lt::renderer::Api::vulkan,
fixture.device(),
fixture.gpu(),
lt::renderer::IBuffer::CreateInfo {
.usage = staging,
.size = size,
.debug_name = "",
}
);
auto map = buffer->map();
expect_eq(map.size(), size);
expect_not_nullptr(map.data());
expect_false(fixture.has_any_messages_of(error));
expect_false(fixture.has_any_messages_of(warning));
}; };
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}; };

View file

@ -11,7 +11,7 @@ module;
#define VK_NO_PROTOTYPES #define VK_NO_PROTOTYPES
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
#define VK_USE_PLATFORM_XLIB_KHR #define VK_USE_PLATFORM_WAYLAND_KHR
#elif defined(LIGHT_PLATFORM_WINDOWS) #elif defined(LIGHT_PLATFORM_WINDOWS)
#define VK_USE_PLATFORM_WIN32_KHR #define VK_USE_PLATFORM_WIN32_KHR
#else #else
@ -21,9 +21,11 @@ module;
#include <vulkan/vulkan.h> #include <vulkan/vulkan.h>
#include <vulkan/vulkan_core.h> #include <vulkan/vulkan_core.h>
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
#include <vulkan/vulkan_xlib.h> struct wl_display;
struct wl_surface;
#include <vulkan/vulkan_wayland.h>
#include <wayland-client.h>
#endif #endif
#if defined(LIGHT_PLATFORM_WINDOWS) #if defined(LIGHT_PLATFORM_WINDOWS)
#include <Windows.h> #include <Windows.h>
@ -61,8 +63,8 @@ namespace constants {
constexpr auto application_version = VK_MAKE_VERSION(1, 0, 0); constexpr auto application_version = VK_MAKE_VERSION(1, 0, 0);
constexpr auto engine_version = VK_MAKE_VERSION(1, 0, 0); constexpr auto engine_version = VK_MAKE_VERSION(1, 0, 0);
constexpr auto api_version = VK_API_VERSION_1_4; constexpr auto api_version = VK_API_VERSION_1_4;
constexpr auto app_name = "load_this_from_envs..."; constexpr auto app_name = "Wayland Vulkan Example";
constexpr auto engine_name = "light_engine_vulkan_renderer"; constexpr auto engine_name = "Wayland Vulkan Example";
constexpr auto max_physical_device_name = VK_MAX_PHYSICAL_DEVICE_NAME_SIZE; constexpr auto max_physical_device_name = VK_MAX_PHYSICAL_DEVICE_NAME_SIZE;
constexpr auto max_memory_types = VK_MAX_MEMORY_TYPES; constexpr auto max_memory_types = VK_MAX_MEMORY_TYPES;
@ -88,7 +90,7 @@ constexpr auto physical_device_properties_2
constexpr auto surface = VK_KHR_SURFACE_EXTENSION_NAME; constexpr auto surface = VK_KHR_SURFACE_EXTENSION_NAME;
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
constexpr auto platform_surface = VK_KHR_XLIB_SURFACE_EXTENSION_NAME; constexpr auto platform_surface = VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME;
#elif defined(LIGHT_PLATFORM_WINDOWS) #elif defined(LIGHT_PLATFORM_WINDOWS)
constexpr auto platform_surface = VK_KHR_WIN32_SURFACE_EXTENSION_NAME; constexpr auto platform_surface = VK_KHR_WIN32_SURFACE_EXTENSION_NAME;
@ -828,9 +830,9 @@ public:
struct CreateInfo struct CreateInfo
{ {
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
Display *display; wl_display *display;
Window window; wl_surface *surface;
#elif defined(LIGHT_PLATFORM_WINDOWS) #elif defined(LIGHT_PLATFORM_WINDOWS)
HWND window; HWND window;
#else #else
@ -1306,8 +1308,10 @@ public:
/** de-allocation functions */ /** de-allocation functions */
void free_memory(VkDeviceMemory memory) const; void free_memory(VkDeviceMemory memory) const;
void free_descriptor_set(VkDescriptorPool descriptor_pool, VkDescriptorSet descriptor_set) void free_descriptor_set(
const; VkDescriptorPool descriptor_pool,
VkDescriptorSet descriptor_set
) const;
/** destroy functions */ /** destroy functions */
void destroy_swapchain(VkSwapchainKHR swapchain) const; void destroy_swapchain(VkSwapchainKHR swapchain) const;
@ -2695,10 +2699,74 @@ namespace api {
PFN_vkSetDebugUtilsObjectNameEXT set_debug_object_name {}; // NOLINT PFN_vkSetDebugUtilsObjectNameEXT set_debug_object_name {}; // NOLINT
} }
constexpr auto to_string(VkResult result) noexcept -> std::string_view
{
switch (result)
{
// clang-format off
case VK_SUCCESS: return "VK_SUCCESS";
case VK_NOT_READY: return "VK_NOT_READY";
case VK_TIMEOUT: return "VK_TIMEOUT";
case VK_EVENT_SET: return "VK_EVENT_SET";
case VK_EVENT_RESET: return "VK_EVENT_RESET";
case VK_INCOMPLETE: return "VK_INCOMPLETE";
case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY";
case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED";
case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST";
case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED";
case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT";
case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT";
case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT";
case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER";
case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS";
case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED";
case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL";
case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN";
case VK_ERROR_VALIDATION_FAILED: return "VK_ERROR_VALIDATION_FAILED";
case VK_ERROR_OUT_OF_POOL_MEMORY: return "VK_ERROR_OUT_OF_POOL_MEMORY";
case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "VK_ERROR_INVALID_EXTERNAL_HANDLE";
case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS";
case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION";
case VK_PIPELINE_COMPILE_REQUIRED: return "VK_PIPELINE_COMPILE_REQUIRED";
case VK_ERROR_NOT_PERMITTED: return "VK_ERROR_NOT_PERMITTED";
case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR";
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR";
case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR";
case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
case VK_ERROR_INVALID_SHADER_NV: return "VK_ERROR_INVALID_SHADER_NV";
case VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR: return "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR";
case VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR";
case VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR";
case VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR";
case VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR";
case VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR: return "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR";
case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
case VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT: return "VK_ERROR_PRESENT_TIMING_QUEUE_FULL_EXT";
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
case VK_THREAD_IDLE_KHR: return "VK_THREAD_IDLE_KHR";
case VK_THREAD_DONE_KHR: return "VK_THREAD_DONE_KHR";
case VK_OPERATION_DEFERRED_KHR: return "VK_OPERATION_DEFERRED_KHR";
case VK_OPERATION_NOT_DEFERRED_KHR: return "VK_OPERATION_NOT_DEFERRED_KHR";
case VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR: return "VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR";
case VK_ERROR_COMPRESSION_EXHAUSTED_EXT: return "VK_ERROR_COMPRESSION_EXHAUSTED_EXT";
case VK_INCOMPATIBLE_SHADER_BINARY_EXT: return "VK_INCOMPATIBLE_SHADER_BINARY_EXT";
case VK_PIPELINE_BINARY_MISSING_KHR: return "VK_PIPELINE_BINARY_MISSING_KHR";
case VK_ERROR_NOT_ENOUGH_SPACE_KHR: return "VK_ERROR_NOT_ENOUGH_SPACE_KHR";
case VK_RESULT_MAX_ENUM: return "VK_RESULT_MAX_ENUM";
default: return"<unknown>";
// clang-format on
}
std::unreachable();
}
void vkc(VkResult result) void vkc(VkResult result)
{ {
if (result) if (result)
{ {
log::error("Checked vulkan call failed with result: {}", to_string(result));
throw std::runtime_error { throw std::runtime_error {
std::format("Vulkan call failed with result: {}", std::to_underlying(result)) std::format("Vulkan call failed with result: {}", std::to_underlying(result))
}; };
@ -2844,7 +2912,7 @@ PFN_vkCmdBeginRendering cmd_begin_rendering {};
PFN_vkCmdEndRendering cmd_end_rendering {}; PFN_vkCmdEndRendering cmd_end_rendering {};
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
PFN_vkCreateXlibSurfaceKHR create_xlib_surface_khr {}; PFN_vkCreateWaylandSurfaceKHR create_wayland_surface_khr {};
#elif defined(LIGHT_PLATFORM_WINDOWS) #elif defined(LIGHT_PLATFORM_WINDOWS)
PFN_vkCreateWin32SurfaceKHR create_win32_surface_khr {}; PFN_vkCreateWin32SurfaceKHR create_win32_surface_khr {};
#else #else
@ -2961,7 +3029,7 @@ void Instance::load_functions()
load_fn(api::get_physical_device_surface_formats, "vkGetPhysicalDeviceSurfaceFormatsKHR"); load_fn(api::get_physical_device_surface_formats, "vkGetPhysicalDeviceSurfaceFormatsKHR");
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
load_fn(api::create_xlib_surface_khr, "vkCreateXlibSurfaceKHR"); load_fn(api::create_wayland_surface_khr, "vkCreateWaylandSurfaceKHR");
#elif defined(LIGHT_PLATFORM_WINDOWS) #elif defined(LIGHT_PLATFORM_WINDOWS)
load_fn(api::create_win32_surface_khr, "vkCreateWin32SurfaceKHR"); load_fn(api::create_win32_surface_khr, "vkCreateWin32SurfaceKHR");
#else #else
@ -3086,13 +3154,15 @@ Instance::Instance(CreateInfo info)
debug::ensure(values, "Failed to get variant from setting.values"); debug::ensure(values, "Failed to get variant from setting.values");
layer_settings.emplace_back(VkLayerSettingEXT { layer_settings.emplace_back(
VkLayerSettingEXT {
.pLayerName = layer.name.c_str(), .pLayerName = layer.name.c_str(),
.pSettingName = setting.name.c_str(), .pSettingName = setting.name.c_str(),
.type = std::visit(layer_setting_type_visitor, setting.values), .type = std::visit(layer_setting_type_visitor, setting.values),
.valueCount = 1u, .valueCount = 1u,
.pValues = values, .pValues = values,
}); }
);
} }
} }
@ -3129,15 +3199,27 @@ Surface::Surface(const Instance &instance, const CreateInfo &info)
: m_instance(instance.m_instance.get()) : m_instance(instance.m_instance.get())
{ {
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
const auto vk_info = VkXlibSurfaceCreateInfoKHR { const auto vk_info = VkWaylandSurfaceCreateInfoKHR {
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
.pNext = {}, .pNext = {},
.flags = {}, .flags = {},
.dpy = info.display, .display = info.display,
.window = info.window, .surface = info.surface,
}; };
vkc(api::create_xlib_surface_khr(instance.get_vk_handle(), &vk_info, nullptr, &m_surface));
log::debug(
"Display proxy's version: {}",
wl_proxy_get_version(std::bit_cast<wl_proxy *>(info.display))
);
log::debug(
"Surface proxy's version: {}",
wl_proxy_get_version(std::bit_cast<wl_proxy *>(info.surface))
);
vkc(api::create_wayland_surface_khr(instance.get_vk_handle(), &vk_info, nullptr, &m_surface));
log::debug("Wayland surface vulkan handle id is: {}", (size_t)m_surface);
#elif defined(LIGHT_PLATFORM_WINDOWS) #elif defined(LIGHT_PLATFORM_WINDOWS)
const auto vk_info = VkWin32SurfaceCreateInfoKHR { const auto vk_info = VkWin32SurfaceCreateInfoKHR {
@ -3284,7 +3366,7 @@ Surface::~Surface()
api::get_physical_device_features(m_physical_device, &features_2); api::get_physical_device_features(m_physical_device, &features_2);
return DescriptorIndexingFeatures { return DescriptorIndexingFeatures {
// clang-format off // clang-format off
.shader_input_attachment_array_dynamic_indexing =true, .shader_input_attachment_array_dynamic_indexing = false,
.shader_uniform_texel_buffer_array_dynamic_indexing = true, .shader_uniform_texel_buffer_array_dynamic_indexing = true,
.shader_storage_texel_buffer_array_dynamic_indexing = true, .shader_storage_texel_buffer_array_dynamic_indexing = true,
.shader_uniform_buffer_array_non_uniform_indexing = true, .shader_uniform_buffer_array_non_uniform_indexing = true,
@ -3581,10 +3663,12 @@ Surface::~Surface()
auto formats = std::vector<Surface::Format> {}; auto formats = std::vector<Surface::Format> {};
for (auto &vk_format : vk_formats) for (auto &vk_format : vk_formats)
{ {
formats.emplace_back(Surface::Format { formats.emplace_back(
Surface::Format {
.format = static_cast<Format>(vk_format.format), .format = static_cast<Format>(vk_format.format),
.color_space = static_cast<ColorSpace>(vk_format.colorSpace), .color_space = static_cast<ColorSpace>(vk_format.colorSpace),
}); }
);
} }
return formats; return formats;
@ -3641,12 +3725,14 @@ Device::Device(const Gpu &gpu, CreateInfo info)
auto vk_queue_infos = std::vector<VkDeviceQueueCreateInfo> {}; auto vk_queue_infos = std::vector<VkDeviceQueueCreateInfo> {};
for (auto queue_family : info.queue_indices) for (auto queue_family : info.queue_indices)
{ {
vk_queue_infos.emplace_back(VkDeviceQueueCreateInfo { vk_queue_infos.emplace_back(
VkDeviceQueueCreateInfo {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = queue_family, .queueFamilyIndex = queue_family,
.queueCount = 1u, .queueCount = 1u,
.pQueuePriorities = &priorities, .pQueuePriorities = &priorities,
}); }
);
} }
auto vk_extension_names = std::vector<const char *> {}; auto vk_extension_names = std::vector<const char *> {};
@ -3782,13 +3868,17 @@ Device::Device(const Gpu &gpu, CreateInfo info)
} }
vkc(api::create_device(gpu.m_physical_device, &vk_info, nullptr, &m_device)); vkc(api::create_device(gpu.m_physical_device, &vk_info, nullptr, &m_device));
log::debug("Created device: 0x{:x}", (size_t)m_device, (size_t)m_device);
} }
Device::~Device() Device::~Device()
{ {
if (m_device) if (m_device)
{ {
log::debug("Destroying device {:x}...", (size_t)m_device);
api::destroy_device(m_device, nullptr); api::destroy_device(m_device, nullptr);
log::debug("...Destroyed device");
std::cout << "D" << std::endl;
} }
} }
@ -4039,8 +4129,10 @@ void Device::free_memory(VkDeviceMemory memory) const
api::free_memory(m_device, memory, nullptr); api::free_memory(m_device, memory, nullptr);
} }
void Device::free_descriptor_set(VkDescriptorPool descriptor_pool, VkDescriptorSet descriptor_set) void Device::free_descriptor_set(
const VkDescriptorPool descriptor_pool,
VkDescriptorSet descriptor_set
) const
{ {
vkc(api::free_descriptor_sets(m_device, descriptor_pool, 1, &descriptor_set)); vkc(api::free_descriptor_sets(m_device, descriptor_pool, 1, &descriptor_set));
} }
@ -4495,6 +4587,9 @@ Swapchain::Swapchain(Device &device, Surface &surface, CreateInfo info)
: m_device(device.m_device.get()) : m_device(device.m_device.get())
, m_swapchain() , m_swapchain()
{ {
log::debug("Wayland surface vulkan handle id is now: 0x{:x}", (size_t)surface.m_surface);
log::debug("Got device for swapchain: 0x{:x}", (size_t)m_device);
auto vk_info = VkSwapchainCreateInfoKHR { auto vk_info = VkSwapchainCreateInfoKHR {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = surface.m_surface, .surface = surface.m_surface,
@ -4513,20 +4608,36 @@ Swapchain::Swapchain(Device &device, Surface &surface, CreateInfo info)
.clipped = VK_TRUE, .clipped = VK_TRUE,
.oldSwapchain = nullptr, .oldSwapchain = nullptr,
}; };
log::debug("Creating swapchain: 0x{:x}", (size_t)m_swapchain);
vkc(api::create_swapchain_khr(m_device, &vk_info, nullptr, &m_swapchain)); vkc(api::create_swapchain_khr(m_device, &vk_info, nullptr, &m_swapchain));
log::debug("Created swapchain: 0x{:x}", (size_t)m_swapchain);
if (info.name.empty()) if (info.name.empty())
{ {
info.name = "<unnamed>"; info.name = "<unnamed>";
} }
device.name(*this, "{}", info.name); device.name(*this, "{}", info.name);
log::debug("Still got device for swapchain: 0x{:x}", (size_t)m_device);
} }
Swapchain::~Swapchain() Swapchain::~Swapchain()
{ {
if (m_device) if (m_device)
{ {
log::debug("Destroyig swapchain...");
log::debug("device: 0x{:x}", (size_t)m_device);
log::debug("swapchain: 0x{:x}", (size_t)m_swapchain);
log::debug("vkDestroySwapchainKHR: 0x{:x}", (size_t)api::destroy_swapchain_khr);
api::destroy_swapchain_khr(m_device, m_swapchain, nullptr); api::destroy_swapchain_khr(m_device, m_swapchain, nullptr);
log::debug("...Destroyed swapchain");
}
else
{
log::debug(
"Skipped destruction of Swapchain due to nulled device: 0{:x}",
(size_t)m_device
);
} }
} }
@ -4583,7 +4694,9 @@ Buffer::Buffer(Device &device, CreateInfo info): m_device(device.m_device.get())
Buffer::~Buffer() Buffer::~Buffer()
{ {
std::cout << "B" << std::endl;
api::destroy_buffer(m_device, m_buffer, nullptr); api::destroy_buffer(m_device, m_buffer, nullptr);
std::cout << "C" << std::endl;
} }
[[nodiscard]] auto Buffer::get_memory_requirements() const -> MemoryRequirements [[nodiscard]] auto Buffer::get_memory_requirements() const -> MemoryRequirements
@ -4669,13 +4782,15 @@ DescriptorSetLayout::DescriptorSetLayout(Device &device, CreateInfo info)
vk_binding_flag_values.reserve(info.bindings.size()); vk_binding_flag_values.reserve(info.bindings.size());
for (auto &binding_info : info.bindings) for (auto &binding_info : info.bindings)
{ {
vk_bindings.emplace_back(VkDescriptorSetLayoutBinding { vk_bindings.emplace_back(
VkDescriptorSetLayoutBinding {
.binding = binding_info.idx, .binding = binding_info.idx,
.descriptorType = static_cast<VkDescriptorType>(binding_info.type), .descriptorType = static_cast<VkDescriptorType>(binding_info.type),
.descriptorCount = binding_info.count, .descriptorCount = binding_info.count,
.stageFlags = binding_info.shader_stages, .stageFlags = binding_info.shader_stages,
.pImmutableSamplers = {}, .pImmutableSamplers = {},
}); }
);
vk_binding_flag_values.emplace_back(binding_info.flags); vk_binding_flag_values.emplace_back(binding_info.flags);
} }
@ -4717,10 +4832,12 @@ DescriptorPool::DescriptorPool(Device &device, CreateInfo info): m_device(device
vk_sizes.reserve(info.sizes.size()); vk_sizes.reserve(info.sizes.size());
for (auto &size : info.sizes) for (auto &size : info.sizes)
{ {
vk_sizes.emplace_back(VkDescriptorPoolSize { vk_sizes.emplace_back(
VkDescriptorPoolSize {
.type = static_cast<VkDescriptorType>(size.type), .type = static_cast<VkDescriptorType>(size.type),
.descriptorCount = size.count, .descriptorCount = size.count,
}); }
);
} }
auto vk_info = VkDescriptorPoolCreateInfo { auto vk_info = VkDescriptorPoolCreateInfo {
@ -4774,12 +4891,14 @@ Pipeline::Pipeline(Device &device, PipelineLayout &layout, CreateInfo info)
auto shader_stages = std::vector<VkPipelineShaderStageCreateInfo> {}; auto shader_stages = std::vector<VkPipelineShaderStageCreateInfo> {};
for (auto &[shader, stage] : info.shaders) for (auto &[shader, stage] : info.shaders)
{ {
shader_stages.emplace_back(VkPipelineShaderStageCreateInfo { shader_stages.emplace_back(
VkPipelineShaderStageCreateInfo {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = static_cast<VkShaderStageFlagBits>(stage), .stage = static_cast<VkShaderStageFlagBits>(stage),
.module = shader.get_vk_handle(), .module = shader.get_vk_handle(),
.pName = "main", .pName = "main",
}); }
);
} }
auto dynamic_states = std::array<VkDynamicState, 2> { auto dynamic_states = std::array<VkDynamicState, 2> {
@ -4862,7 +4981,8 @@ Pipeline::Pipeline(Device &device, PipelineLayout &layout, CreateInfo info)
.colorAttachmentCount = static_cast<uint32_t>(color_attachment_formats.size()), .colorAttachmentCount = static_cast<uint32_t>(color_attachment_formats.size()),
.pColorAttachmentFormats = std::bit_cast<VkFormat *>(color_attachment_formats.data()), .pColorAttachmentFormats = std::bit_cast<VkFormat *>(color_attachment_formats.data()),
.depthAttachmentFormat = info.attachment_state.depth_attachment ? .depthAttachmentFormat = info.attachment_state.depth_attachment ?
static_cast<VkFormat>(*info.attachment_state.depth_attachment static_cast<VkFormat>(
*info.attachment_state.depth_attachment
) : ) :
VK_FORMAT_UNDEFINED, VK_FORMAT_UNDEFINED,
@ -4914,11 +5034,13 @@ PipelineLayout::PipelineLayout(Device &device, CreateInfo info): m_device(device
for (const auto &range : info.push_constant_ranges) for (const auto &range : info.push_constant_ranges)
{ {
vk_push_constant_ranges.emplace_back(VkPushConstantRange { vk_push_constant_ranges.emplace_back(
VkPushConstantRange {
.stageFlags = range.shader_stages, .stageFlags = range.shader_stages,
.offset = range.offset, .offset = range.offset,
.size = range.size, .size = range.size,
}); }
);
} }
for (const auto &layout : info.descriptor_set_layouts) for (const auto &layout : info.descriptor_set_layouts)
@ -4983,6 +5105,7 @@ Messenger::Messenger(Instance &instance, CreateInfo info): m_instance(instance.g
Messenger::~Messenger() Messenger::~Messenger()
{ {
api::destroy_debug_messenger(m_instance, m_messenger, nullptr); api::destroy_debug_messenger(m_instance, m_messenger, nullptr);
std::cout << "C1" << std::endl;
} }
[[nodiscard]] [[nodiscard]]

View file

@ -43,18 +43,18 @@ Surface::Surface(IInstance *instance, const ecs::Entity &surface_entity)
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
debug::ensure( debug::ensure(
component.get_native_data().display, component.get_native_data().display,
"Failed to initialize vk::Surface: null x-display" "Failed to initialize vk::Surface: null Wayland display"
); );
debug::ensure( debug::ensure(
component.get_native_data().window, component.get_native_data().surface,
"Failed to initialize vk::Surface: null x-window" "Failed to initialize vk::Surface: null Wayland surface"
); );
m_surface = vk::Surface( m_surface = vk::Surface(
static_cast<Instance *>(instance)->vk(), static_cast<Instance *>(instance)->vk(),
vk::Surface::CreateInfo { vk::Surface::CreateInfo {
.display = component.get_native_data().display, .display = component.get_native_data().display,
.window = component.get_native_data().window, .surface = component.get_native_data().surface,
} }
); );

View file

@ -83,7 +83,7 @@ Swapchain::Swapchain(ISurface *surface, IGpu *gpu, IDevice *device)
{ {
static auto idx = 0u; static auto idx = 0u;
const auto capabilities = m_gpu->vk().get_surface_capabilities(m_surface->vk()); auto capabilities = m_gpu->vk().get_surface_capabilities(m_surface->vk());
const auto formats = m_gpu->vk().get_surface_formats(m_surface->vk()); const auto formats = m_gpu->vk().get_surface_formats(m_surface->vk());
// TODO(Light): parameterize // TODO(Light): parameterize
@ -91,6 +91,19 @@ Swapchain::Swapchain(ISurface *surface, IGpu *gpu, IDevice *device)
const auto surface_format = formats.front(); const auto surface_format = formats.front();
m_format = surface_format.format; m_format = surface_format.format;
if (capabilities.current_extent.x == std::numeric_limits<std::uint32_t>::max())
{
log::info(
"Vulkan surface capabilities current extent is uint32 max... This indicates that the "
"surface size will be determined by the extent of a swapchain targeting the surface."
);
// TODO(Light): Take surface extent as swapchain creation argument...
capabilities.current_extent.x = 800u;
capabilities.current_extent.y = 600u;
}
m_swapchain = vk::Swapchain( m_swapchain = vk::Swapchain(
m_device->vk(), m_device->vk(),
m_surface->vk(), m_surface->vk(),
@ -101,7 +114,7 @@ Swapchain::Swapchain(ISurface *surface, IGpu *gpu, IDevice *device)
.extent = capabilities.current_extent, .extent = capabilities.current_extent,
.min_image_count = get_optimal_image_count(capabilities, desired_image_count), .min_image_count = get_optimal_image_count(capabilities, desired_image_count),
.queue_family_indices = m_device->get_family_indices(), .queue_family_indices = m_device->get_family_indices(),
.present_mode = vk::Swapchain::PresentMode::immediate, .present_mode = vk::Swapchain::PresentMode::mailbox,
.pre_transform = capabilities.current_transform, .pre_transform = capabilities.current_transform,
.name = std::format("swapchain {}", idx++), .name = std::format("swapchain {}", idx++),
} }

View file

@ -1,6 +1,7 @@
module; module;
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
typedef struct _XDisplay Display; struct wl_display;
struct wl_surface;
#else defined(LIGHT_PLATFORM_WINDOWS) #else defined(LIGHT_PLATFORM_WINDOWS)
#include <Windows.h> #include <Windows.h>
#endif #endif
@ -43,9 +44,9 @@ public:
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
struct NativeData struct NativeData
{ {
Display *display; wl_display *display;
std::uint32_t window;
unsigned long wm_delete_message; wl_surface *surface;
}; };
#elif defined(LIGHT_PLATFORM_WINDOWS) #elif defined(LIGHT_PLATFORM_WINDOWS)
struct NativeData struct NativeData

View file

@ -0,0 +1,28 @@
import test.test;
import test.expects;
import surface.system;
import surface.events;
import surface.requests;
import ecs.registry;
import memory.scope;
import memory.reference;
import logger;
import math.vec2;
import app.system;
import std;
using ::lt::surface::SurfaceComponent;
using ::lt::surface::System;
using ::lt::test::Case;
using ::lt::test::expect_eq;
using ::lt::test::expect_ne;
using ::lt::test::expect_not_nullptr;
using ::lt::test::expect_throw;
using ::lt::test::Suite;
using ::std::ignore;
using ::lt::test::operator""_suite;
Suite raii = "platform_linux_raii"_suite = [] {
auto registry = lt::memory::create_ref<lt::ecs::Registry>();
std::ignore = System { registry };
};

View file

@ -1,13 +1,7 @@
module; module;
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
#define _POSIX_C_SOURCE 200112L
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <wayland-protocols/xdg-shell.h> #include <xdg-shell.h>
#else #else
#error "Unsupported platform" #error "Unsupported platform"
#endif #endif
@ -21,6 +15,7 @@ import ecs.registry;
import math.vec2; import math.vec2;
import surface.requests; import surface.requests;
import memory.reference; import memory.reference;
import memory.null_on_move;
import std; import std;
export namespace lt::surface { export namespace lt::surface {
@ -55,7 +50,7 @@ public:
private: private:
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
static void registry_handle_global( static void handle_globals(
void *data, void *data,
wl_registry *registry, wl_registry *registry,
std::uint32_t name, std::uint32_t name,
@ -89,19 +84,15 @@ private:
app::TickResult m_last_tick_result; app::TickResult m_last_tick_result;
#if defined(LIGHT_PLATFORM_LINUX) #if defined(LIGHT_PLATFORM_LINUX)
wl_display *m_wl_display {}; memory::NullOnMove<wl_display *> m_wl_display {};
wl_registry *m_wl_registry {}; memory::NullOnMove<wl_registry *> m_wl_registry {};
wl_registry_listener m_wl_registry_listener {}; wl_registry_listener m_wl_registry_listener {};
wl_compositor *m_wl_compositor {}; memory::NullOnMove<wl_compositor *> m_wl_compositor {};
wl_surface *m_wl_surface {}; memory::NullOnMove<xdg_wm_base *> m_shell = {};
wl_shm *m_wl_shm {};
wl_shm_pool *m_wl_shm_pool {};
#endif #endif
}; };
@ -112,7 +103,46 @@ namespace lt::surface {
#ifdef LIGHT_PLATFORM_LINUX #ifdef LIGHT_PLATFORM_LINUX
void System::registry_handle_global( void handle_shell_ping(void *data, xdg_wm_base *shell, std::uint32_t serial)
{
std::ignore = data;
xdg_wm_base_pong(shell, serial);
}
const auto shell_listener = xdg_wm_base_listener {
.ping = &handle_shell_ping,
};
void handle_shell_surface_configure(void *data, xdg_surface *shell_surface, std::uint32_t serial)
{
std::ignore = data;
xdg_surface_ack_configure(shell_surface, serial);
}
const auto shell_surface_listener = xdg_surface_listener {
.configure = &handle_shell_surface_configure
};
void handle_toplevel_configure(
void *data,
xdg_toplevel *toplevel,
std::int32_t width,
std::int32_t height,
wl_array *states
)
{
// TODO(Light): handle resizing
}
void handle_toplevel_close(void *data, xdg_toplevel *toplevel)
{
// TODO(Light): handle quitting
}
const auto toplevel_listener = xdg_toplevel_listener {
.configure = &handle_toplevel_configure,
.close = &handle_toplevel_close,
};
void System::handle_globals(
void *data, void *data,
wl_registry *registry, wl_registry *registry,
std::uint32_t name, std::uint32_t name,
@ -123,42 +153,22 @@ void System::registry_handle_global(
{ {
auto *system = std::bit_cast<System *>(data); auto *system = std::bit_cast<System *>(data);
// log::trace("Registry global:");
// log::trace("\tinterface: {}", interface);
// log::trace("\tversion: {}", version);
// log::trace("\tname: {}", name);
if (std::strcmp(interface, wl_compositor_interface.name) == 0) if (std::strcmp(interface, wl_compositor_interface.name) == 0)
{ {
system->m_wl_compositor = std::bit_cast<wl_compositor *>( system->m_wl_compositor = std::bit_cast<wl_compositor *>(
wl_registry_bind(registry, name, &wl_compositor_interface, 4) wl_registry_bind(registry, name, &wl_compositor_interface, 1)
); );
log::info("Bound successfuly to the wl_compositor global"); log::info("Bound successfuly to the wl_compositor global");
system->m_wl_surface = wl_compositor_create_surface(system->m_wl_compositor);
if (system->m_wl_surface)
{
log::info("Created a wl_surface from the compositor");
}
else
{
log::critical("Failed to create a wl_surface from the compositor");
std::terminate();
}
} }
if (std::strcmp(interface, wl_shm_interface.name) == 0) if (std::strcmp(interface, xdg_wm_base_interface.name) == 0)
{ {
system->m_wl_shm = std::bit_cast<wl_shm *>( system->m_shell = std::bit_cast<xdg_wm_base *>(
wl_registry_bind(registry, name, &wl_shm_interface, 1) wl_registry_bind(registry, name, &xdg_wm_base_interface, 1)
); );
xdg_wm_base_add_listener(system->m_shell, &shell_listener, {});
log::info("Bound successfuly to the wl_shm global"); log::info("Bound successfuly to the xdg_wm_base global");
} }
if (std::strcmp(interface, xdg_))
} }
void registry_handle_global_remove(void *data, wl_registry *registry, std::uint32_t name) void registry_handle_global_remove(void *data, wl_registry *registry, std::uint32_t name)
@ -167,121 +177,43 @@ void registry_handle_global_remove(void *data, wl_registry *registry, std::uint3
log::trace("\tname: {}", name); log::trace("\tname: {}", name);
} }
void read_name(char *buffer)
{
auto time_spec = timespec {};
clock_gettime(CLOCK_REALTIME, &time_spec);
auto nanoseconds = time_spec.tv_nsec;
for (auto idx = std::uint32_t { 0u }; idx < 6u; ++idx)
{
buffer[idx] = 'A' + (nanoseconds & 15) + (nanoseconds & 16) * 2; // NOLINT
nanoseconds >>= 5; // NOLINT
}
}
[[nodiscard]] auto create_shm_file() -> int
{
auto retries = 100u;
do // NOLINT
{
char name[] = "/wl_shm-XXXXXX";
read_name(name + sizeof(name) - 7);
--retries;
auto file_descriptor = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (file_descriptor >= 0)
{
shm_unlink(name);
return file_descriptor;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
[[nodiscard]] auto allocate_shm_file(std::size_t size) -> int
{
auto file_descriptor = create_shm_file();
if (file_descriptor < 0)
{
return -1;
}
auto ret = 0;
do // NOLINT
{
ret = ftruncate(file_descriptor, size); // NOLINT
} while (ret < 0 && errno == EINTR);
if (ret < 0)
{
close(file_descriptor);
return -1;
}
return file_descriptor;
}
System::System(memory::Ref<ecs::Registry> registry) System::System(memory::Ref<ecs::Registry> registry)
: m_wl_registry_listener( : m_wl_registry_listener(
{ {
.global = registry_handle_global, .global = handle_globals,
.global_remove = registry_handle_global_remove, .global_remove = registry_handle_global_remove,
} }
) )
, m_registry(std::move(registry))
{ {
// NOLINTNEXTLINE // NOLINTNEXTLINE
m_wl_display = wl_display_connect({}); m_wl_display = wl_display_connect({});
debug::ensure(m_wl_display, "Failed to connect to Wayland display"); debug::ensure(m_wl_display, "Failed to connect to Wayland display");
log::info("Wayland connection established");
// NOLINTNEXTLINE // NOLINTNEXTLINE
m_wl_registry = wl_display_get_registry(m_wl_display); m_wl_registry = wl_display_get_registry(m_wl_display);
debug::ensure(m_wl_registry, "Failed to get Wayland display's registry");
// TODO(Light): "this" could be moved around... replace with a pointer to some heap allocation // TODO(Light): "this" could be moved around... replace with a pointer to some heap allocation
wl_registry_add_listener(m_wl_registry, &m_wl_registry_listener, this); wl_registry_add_listener(m_wl_registry, &m_wl_registry_listener, this);
wl_display_roundtrip(m_wl_display); wl_display_roundtrip(m_wl_display);
debug::ensure(m_wl_compositor, "Could not bind to the Wayland's compositor global"); debug::ensure(m_wl_compositor, "Failed to bind to the Wayland's compositor global");
debug::ensure(m_wl_shm, "Could not bind to the Wayland's compositor global"); debug::ensure(m_shell, "Failed to bind to the Wayland's XDG-shell global");
const auto width = 1080u;
const auto height = 1920u;
const auto stride = 4u;
const auto shm_pool_size = width * stride * height * 2;
auto file_descriptor = allocate_shm_file(shm_pool_size);
auto *pool_data = std::bit_cast<std::uint8_t *>(
mmap({}, shm_pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, 0)
);
m_wl_shm_pool = wl_shm_create_pool(m_wl_shm, file_descriptor, shm_pool_size);
debug::ensure(
m_wl_shm_pool,
"Failed to create Wayland shared memory pool of size: {}",
shm_pool_size
);
log::info("Created Wayland shared memory pool size of: {}", shm_pool_size);
auto idx = 0;
auto offset = width * height * stride * idx;
auto *wl_buffer = wl_shm_pool_create_buffer(
m_wl_shm_pool,
offset,
width,
height,
width * stride,
WL_SHM_FORMAT_XRGB8888,
);
} }
System::~System() System::~System()
{ {
if (m_wl_display)
{
log::debug("Closing Wayland display...");
wl_display_disconnect(m_wl_display); wl_display_disconnect(m_wl_display);
log::debug("Closed Wayland display");
}
else
{
log::debug("Wayland display nulled on move!");
}
} }
void System::on_register() void System::on_register()
@ -294,6 +226,35 @@ void System::on_unregister()
void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::CreateInfo info) void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::CreateInfo info)
{ {
auto &component = m_registry->add<SurfaceComponent>(entity, info);
auto &surface = m_registry->get<SurfaceComponent>(entity);
const auto &resolution = surface.get_resolution();
const auto &position = surface.get_position();
auto *wayland_surface = (wl_surface *)nullptr;
auto *shell_surface = (xdg_surface *)nullptr;
auto *shell_toplevel = (xdg_toplevel *)nullptr;
wayland_surface = wl_compositor_create_surface(m_wl_compositor);
debug::ensure(wayland_surface, "Failed to create Wayland surface");
shell_surface = xdg_wm_base_get_xdg_surface(m_shell, wayland_surface);
debug::ensure(shell_surface, "Failed to get XDG-shell surface");
xdg_surface_add_listener(shell_surface, &shell_surface_listener, {});
shell_toplevel = xdg_surface_get_toplevel(shell_surface);
debug::ensure(shell_toplevel, "Failed to get XDG-shell toplevel");
xdg_toplevel_add_listener(shell_toplevel, &toplevel_listener, {});
xdg_toplevel_set_title(shell_toplevel, "Wayland Vulkan Example");
xdg_toplevel_set_app_id(shell_toplevel, "Wayland Vulkan Example");
wl_surface_commit(wayland_surface);
wl_display_roundtrip(m_wl_display);
wl_surface_commit(wayland_surface);
surface.m_native_data.surface = wayland_surface;
surface.m_native_data.display = m_wl_display;
} }
void System::tick(app::TickInfo tick) void System::tick(app::TickInfo tick)

View file

@ -1,284 +1,290 @@
import test.test; // Suite raii = "raii"_suite = [] {
import test.expects; // Case { "happy path won't throw" } = [] {
import surface.system; // auto fixture = Fixture {};
import surface.events; // ignore = System { fixture.registry() };
import surface.requests; // };
import ecs.registry; //
import memory.scope; // import test.test;
import memory.reference; // import test.expects;
import logger; // import surface.system;
import math.vec2; // import surface.events;
import app.system; // import surface.requests;
import std; // import ecs.registry;
// import memory.scope;
using ::lt::surface::SurfaceComponent; // import memory.reference;
using ::lt::surface::System; // import logger;
using ::lt::test::Case; // import math.vec2;
using ::lt::test::expect_eq; // import app.system;
using ::lt::test::expect_ne; // import std;
using ::lt::test::expect_not_nullptr; //
using ::lt::test::expect_throw; // using ::lt::surface::SurfaceComponent;
using ::lt::test::Suite; // using ::lt::surface::System;
using ::std::ignore; // using ::lt::test::Case;
using ::lt::test::operator""_suite; // using ::lt::test::expect_eq;
// using ::lt::test::expect_ne;
[[nodiscard]] auto tick_info() -> lt::app::TickInfo // using ::lt::test::expect_not_nullptr;
{ // using ::lt::test::expect_throw;
return { // using ::lt::test::Suite;
.delta_time = std::chrono::milliseconds { 16 }, // using ::std::ignore;
.budget = std::chrono::milliseconds { 10 }, // using ::lt::test::operator""_suite;
.start_time = std::chrono::steady_clock::now(), //
}; // [[nodiscard]] auto tick_info() -> lt::app::TickInfo
} // {
// return {
constexpr auto title = "TestWindow"; // .delta_time = std::chrono::milliseconds { 16 },
constexpr auto width = 800u; // .budget = std::chrono::milliseconds { 10 },
constexpr auto height = 600u; // .start_time = std::chrono::steady_clock::now(),
constexpr auto vsync = true; // };
constexpr auto visible = false; // }
//
template<class... Ts> // constexpr auto title = "TestWindow";
struct overloads: Ts... // constexpr auto width = 800u;
{ // constexpr auto height = 600u;
using Ts::operator()...; // constexpr auto vsync = true;
}; // constexpr auto visible = false;
//
class Fixture // template<class... Ts>
{ // struct overloads: Ts...
public: // {
[[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry> // using Ts::operator()...;
{ // };
return m_registry; //
} // class Fixture
// {
auto create_component( // public:
SurfaceComponent::CreateInfo info = SurfaceComponent::CreateInfo { // [[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry>
.title = title, // {
.resolution = { width, height }, // return m_registry;
.vsync = vsync, // }
.visible = visible, //
} // auto create_component(
) -> std::optional<SurfaceComponent *> // SurfaceComponent::CreateInfo info = SurfaceComponent::CreateInfo {
{ // .title = title,
auto entity = m_registry->create_entity(); // .resolution = { width, height },
m_system.create_surface_component(entity, info); // .vsync = vsync,
// .visible = visible,
return &m_registry->get<SurfaceComponent>(entity); // }
} // ) -> std::optional<SurfaceComponent *>
// {
void check_values(SurfaceComponent *component) // auto entity = m_registry->create_entity();
{ // m_system.create_surface_component(entity, info);
#ifdef LIGHT_PLATFORM_LINUX //
expect_not_nullptr(component->get_native_data().display); // return &m_registry->get<SurfaceComponent>(entity);
expect_ne(component->get_native_data().window, 0); // }
#endif //
// void check_values(SurfaceComponent *component)
expect_eq(component->get_resolution().x, width); // {
expect_eq(component->get_resolution().y, height); // #ifdef LIGHT_PLATFORM_LINUX
expect_eq(component->get_title(), title); // expect_not_nullptr(component->get_native_data().display);
expect_eq(component->is_vsync(), vsync); // expect_ne(component->get_native_data().window, 0);
expect_eq(component->is_visible(), visible); // #endif
} //
// expect_eq(component->get_resolution().x, width);
private: // expect_eq(component->get_resolution().y, height);
lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>(); // expect_eq(component->get_title(), title);
// expect_eq(component->is_vsync(), vsync);
System m_system { m_registry }; // expect_eq(component->is_visible(), visible);
}; // }
//
// private:
Suite raii = "raii"_suite = [] { // lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>();
Case { "happy path won't throw" } = [] { //
auto fixture = Fixture {}; // System m_system { m_registry };
ignore = System { fixture.registry() }; // };
}; //
//
Case { "many won't freeze/throw" } = [] { // Suite raii = "raii"_suite = [] {
auto fixture = Fixture {}; // Case { "happy path won't throw" } = [] {
for (auto idx : std::views::iota(0, 250)) // auto fixture = Fixture {};
{ // ignore = System { fixture.registry() };
ignore = System { fixture.registry() }; // };
} //
}; // Case { "many won't freeze/throw" } = [] {
// auto fixture = Fixture {};
Case { "unhappy path throws" } = [] { // for (auto idx : std::views::iota(0, 250))
expect_throw([] { ignore = System { {} }; }); // {
}; // ignore = System { fixture.registry() };
// }
Case { "post construct has correct state" } = [] { // };
auto fixture = Fixture {}; //
auto system = System { fixture.registry() }; // Case { "unhappy path throws" } = [] {
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0); // expect_throw([] { ignore = System { {} }; });
}; // };
//
Case { "post destruct has correct state" } = [] { // Case { "post construct has correct state" } = [] {
auto fixture = Fixture {}; // auto fixture = Fixture {};
auto system = lt::memory::create_scope<System>(fixture.registry()); // auto system = System { fixture.registry() };
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
fixture.create_component(); // };
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1); //
// Case { "post destruct has correct state" } = [] {
system.reset(); // auto fixture = Fixture {};
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0); // auto system = lt::memory::create_scope<System>(fixture.registry());
}; //
}; // fixture.create_component();
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
Suite system_events = "system_events"_suite = [] { //
Case { "on_register won't throw" } = [] { // system.reset();
auto fixture = Fixture {}; // expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
auto system = System { fixture.registry() }; // };
// };
system.on_register(); //
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0); // Suite system_events = "system_events"_suite = [] {
}; // Case { "on_register won't throw" } = [] {
// auto fixture = Fixture {};
Case { "on_unregister won't throw" } = [] { // auto system = System { fixture.registry() };
auto fixture = Fixture {}; //
auto system = System { fixture.registry() }; // system.on_register();
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
system.on_register(); // };
system.on_unregister(); //
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0); // Case { "on_unregister won't throw" } = [] {
}; // auto fixture = Fixture {};
}; // auto system = System { fixture.registry() };
//
Suite registry_events = "registry_events"_suite = [] { // system.on_register();
Case { "on_construct<SurfaceComponent> initializes component" } = [] { // system.on_unregister();
auto fixture = Fixture {}; // expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
// };
const auto &component = fixture.create_component(); // };
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1); //
fixture.check_values(*component); // Suite registry_events = "registry_events"_suite = [] {
}; // Case { "on_construct<SurfaceComponent> initializes component" } = [] {
// auto fixture = Fixture {};
Case { "unhappy on_construct<SurfaceComponent> throws" } = [] { //
auto fixture = Fixture {}; // const auto &component = fixture.create_component();
auto system = System { fixture.registry() }; // expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
// fixture.check_values(*component);
expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); }); // };
//
expect_throw([&] { fixture.create_component({ .resolution = { 0, height } }); }); // Case { "unhappy on_construct<SurfaceComponent> throws" } = [] {
// auto fixture = Fixture {};
expect_throw([&] { // auto system = System { fixture.registry() };
fixture.create_component( //
{ .title = "", .resolution = { SurfaceComponent::max_dimension + 1, height } } // expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); });
); //
}); // expect_throw([&] { fixture.create_component({ .resolution = { 0, height } }); });
//
expect_throw([&] { // expect_throw([&] {
fixture.create_component( // fixture.create_component(
{ .title = "", .resolution = { width, SurfaceComponent::max_dimension + 1 } } // { .title = "", .resolution = { SurfaceComponent::max_dimension + 1, height } }
); // );
}); // });
//
auto big_str = std::string {}; // expect_throw([&] {
big_str.resize(SurfaceComponent::max_title_length + 1); // fixture.create_component(
expect_throw([&] { // { .title = "", .resolution = { width, SurfaceComponent::max_dimension + 1 } }
fixture.create_component({ .title = big_str, .resolution = { width, height } }); // );
}); // });
}; //
// auto big_str = std::string {};
Case { "unhappy on_construct<SurfaceComponent> removes component" } = [] { // big_str.resize(SurfaceComponent::max_title_length + 1);
auto fixture = Fixture {}; // expect_throw([&] {
auto system = System { fixture.registry() }; // fixture.create_component({ .title = big_str, .resolution = { width, height } });
// });
expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); }); // };
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0); //
}; // Case { "unhappy on_construct<SurfaceComponent> removes component" } = [] {
// auto fixture = Fixture {};
Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] { // auto system = System { fixture.registry() };
auto fixture = Fixture {}; //
auto system = lt::memory::create_scope<System>(fixture.registry()); // expect_throw([&] { fixture.create_component({ .resolution = { width, 0 } }); });
// expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
const auto &component = fixture.create_component(); // };
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1); //
fixture.check_values(*component); // Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] {
// auto fixture = Fixture {};
system.reset(); // auto system = lt::memory::create_scope<System>(fixture.registry());
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0); //
}; // const auto &component = fixture.create_component();
}; // expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 1);
// fixture.check_values(*component);
Suite tick = "tick"_suite = [] { //
Case { "ticking on empty registry won't throw" } = [] { // system.reset();
auto fixture = Fixture {}; // expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
System { fixture.registry() }.tick(tick_info()); // };
}; // };
//
Case { "ticking on non-empty registry won't throw" } = [] { // Suite tick = "tick"_suite = [] {
auto fixture = Fixture {}; // Case { "ticking on empty registry won't throw" } = [] {
auto system = System { fixture.registry() }; // auto fixture = Fixture {};
// System { fixture.registry() }.tick(tick_info());
fixture.create_component(); // };
system.tick(tick_info()); //
}; // Case { "ticking on non-empty registry won't throw" } = [] {
}; // auto fixture = Fixture {};
// auto system = System { fixture.registry() };
Suite tick_handles_events = "tick_handles_events"_suite = [] { //
Case { "ticking clears previous tick's events" } = [] { // fixture.create_component();
auto fixture = Fixture {}; // system.tick(tick_info());
auto system = System { fixture.registry() }; // };
auto &surface = **fixture.create_component(); // };
//
// flush window-creation events // Suite tick_handles_events = "tick_handles_events"_suite = [] {
system.tick(tick_info()); // Case { "ticking clears previous tick's events" } = [] {
expect_eq(surface.peek_events().size(), 0); // auto fixture = Fixture {};
// auto system = System { fixture.registry() };
surface.push_event(lt::surface::MovedEvent({}, {})); // auto &surface = **fixture.create_component();
expect_eq(surface.peek_events().size(), 1); //
// // flush window-creation events
surface.push_event(lt::surface::ButtonPressedEvent({})); // system.tick(tick_info());
expect_eq(surface.peek_events().size(), 2); // expect_eq(surface.peek_events().size(), 0);
//
system.tick(tick_info()); // surface.push_event(lt::surface::MovedEvent({}, {}));
expect_eq(surface.peek_events().size(), 0); // expect_eq(surface.peek_events().size(), 1);
}; //
}; // surface.push_event(lt::surface::ButtonPressedEvent({}));
// expect_eq(surface.peek_events().size(), 2);
Suite tick_handles_requests = "tick_handles_requests"_suite = [] { //
Case { "ticking clears requests" } = [] { // system.tick(tick_info());
auto fixture = Fixture {}; // expect_eq(surface.peek_events().size(), 0);
auto system = System { fixture.registry() }; // };
auto &surface = **fixture.create_component(); // };
//
constexpr auto title = "ABC"; // Suite tick_handles_requests = "tick_handles_requests"_suite = [] {
constexpr auto position = lt::math::ivec2 { 50, 50 }; // Case { "ticking clears requests" } = [] {
constexpr auto resolution = lt::math::uvec2 { 50, 50 }; // auto fixture = Fixture {};
// auto system = System { fixture.registry() };
expect_eq(surface.peek_requests().size(), 0); // auto &surface = **fixture.create_component();
//
surface.push_request(lt::surface::ModifyVisibilityRequest(true)); // constexpr auto title = "ABC";
expect_eq(surface.peek_requests().size(), 1); // constexpr auto position = lt::math::ivec2 { 50, 50 };
system.tick(tick_info()); // constexpr auto resolution = lt::math::uvec2 { 50, 50 };
expect_eq(surface.peek_requests().size(), 0); //
// expect_eq(surface.peek_requests().size(), 0);
surface.push_request(lt::surface::ModifyTitleRequest(title)); //
expect_eq(surface.peek_requests().size(), 1); // surface.push_request(lt::surface::ModifyVisibilityRequest(true));
// expect_eq(surface.peek_requests().size(), 1);
surface.push_request(lt::surface::ModifyResolutionRequest(resolution)); // system.tick(tick_info());
surface.push_request(lt::surface::ModifyPositionRequest(position)); // expect_eq(surface.peek_requests().size(), 0);
expect_eq(surface.peek_requests().size(), 1 + 2); //
// surface.push_request(lt::surface::ModifyTitleRequest(title));
surface.push_request(lt::surface::ModifyVisibilityRequest(false)); // expect_eq(surface.peek_requests().size(), 1);
surface.push_request(lt::surface::ModifyVisibilityRequest(true)); //
surface.push_request(lt::surface::ModifyVisibilityRequest(false)); // surface.push_request(lt::surface::ModifyResolutionRequest(resolution));
expect_eq(surface.peek_requests().size(), 1 + 2 + 3); // surface.push_request(lt::surface::ModifyPositionRequest(position));
// expect_eq(surface.peek_requests().size(), 1 + 2);
system.tick(tick_info()); //
expect_eq(surface.peek_requests().size(), 0); // surface.push_request(lt::surface::ModifyVisibilityRequest(false));
// surface.push_request(lt::surface::ModifyVisibilityRequest(true));
expect_eq(surface.get_title(), title); // surface.push_request(lt::surface::ModifyVisibilityRequest(false));
expect_eq(surface.get_position(), position); // expect_eq(surface.peek_requests().size(), 1 + 2 + 3);
expect_eq(surface.get_resolution(), resolution); //
// system.tick(tick_info());
lt::log::debug("EVENT COUNT: {}", surface.peek_events().size()); // expect_eq(surface.peek_requests().size(), 0);
for (const auto &event : surface.peek_events()) //
{ // expect_eq(surface.get_title(), title);
const auto visitor = overloads { // expect_eq(surface.get_position(), position);
[&](auto event) { lt::log::debug("event: {}", event.to_string()); }, // expect_eq(surface.get_resolution(), resolution);
}; //
// lt::log::debug("EVENT COUNT: {}", surface.peek_events().size());
std::visit(visitor, event); // for (const auto &event : surface.peek_events())
} // {
}; // const auto visitor = overloads {
}; // [&](auto event) { lt::log::debug("event: {}", event.to_string()); },
// };
//
// std::visit(visitor, event);
// }
// };
// };

View file

@ -0,0 +1 @@
*