From 9f2dab344d263a917efd1eba6ded0c04937acad8 Mon Sep 17 00:00:00 2001 From: Slendi Date: Mon, 12 Jan 2026 16:38:28 +0200 Subject: [PATCH] DRM/KMS backebd Signed-off-by: Slendi --- src/Application.cpp | 211 +++++++++++++++++++++++++---------------- src/Application.h | 6 ++ src/VulkanRenderer.cpp | 208 +++++++++++++++++++++++++++++++++++----- src/VulkanRenderer.h | 20 ++++ 4 files changed, 341 insertions(+), 104 deletions(-) diff --git a/src/Application.cpp b/src/Application.cpp index 321fa32..4485fb0 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -405,22 +405,35 @@ namespace Lunar { Application::Application() { - if (!SDL_Init(SDL_INIT_VIDEO)) { - std::println(std::cerr, "Failed to initialize SDL."); - throw std::runtime_error("App init fail"); + auto const *display_env = getenv("DISPLAY"); + auto const *wayland_env = getenv("WAYLAND_DISPLAY"); + bool const has_display + = (display_env && *display_env) || (wayland_env && *wayland_env); + m_backend = has_display ? Backend::SDL : Backend::KMS; + + if (m_backend == Backend::SDL) { + if (!SDL_Init(SDL_INIT_VIDEO)) { + std::println(std::cerr, "Failed to initialize SDL."); + throw std::runtime_error("App init fail"); + } + + m_window = SDL_CreateWindow( + "Lunar", 1280, 720, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); + if (!m_window) { + m_logger.err("Failed to create SDL window"); + throw std::runtime_error("App init fail"); + } + + m_renderer = std::make_unique(m_window, m_logger); + + m_window_focused + = (SDL_GetWindowFlags(m_window) & SDL_WINDOW_INPUT_FOCUS) != 0; + } else { + m_logger.info("No display server detected; using KMS backend"); + m_renderer = std::make_unique( + VulkanRenderer::KmsSurfaceConfig {}, m_logger); + m_window_focused = true; } - - m_window = SDL_CreateWindow( - "Lunar", 1280, 720, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE); - if (!m_window) { - m_logger.err("Failed to create SDL window"); - throw std::runtime_error("App init fail"); - } - - m_renderer = std::make_unique(m_window, m_logger); - - m_window_focused - = (SDL_GetWindowFlags(m_window) & SDL_WINDOW_INPUT_FOCUS) != 0; m_renderer->set_antialiasing_immediate( VulkanRenderer::AntiAliasingKind::MSAA_4X); @@ -429,7 +442,8 @@ Application::Application() init_input(); - mouse_captured(true); + if (m_backend == Backend::SDL) + mouse_captured(true); m_logger.info("App init done!"); @@ -452,14 +466,20 @@ Application::~Application() shutdown_input(); - SDL_DestroyWindow(m_window); - SDL_Quit(); + if (m_backend == Backend::SDL) { + SDL_DestroyWindow(m_window); + SDL_Quit(); + } m_logger.info("App destroy done!"); } auto Application::binary_directory() const -> std::filesystem::path { + if (m_backend != Backend::SDL) { + return std::filesystem::current_path(); + } + auto const *base_path = SDL_GetBasePath(); if (!base_path) { return std::filesystem::current_path(); @@ -533,15 +553,28 @@ auto Application::run() -> void { SDL_Event e; + bool const use_sdl = (m_backend == Backend::SDL); + bool const use_imgui = use_sdl; - ImGuiIO &io = ImGui::GetIO(); - io.IniFilename = nullptr; + if (use_imgui) { + ImGuiIO &io = ImGui::GetIO(); + io.IniFilename = nullptr; + } uint64_t last { 0 }; float fps { 0.0f }; while (m_running) { GZoneScopedN("Frame"); - uint64_t now { SDL_GetTicks() }; + uint64_t now { 0 }; + if (use_sdl) { + now = SDL_GetTicks(); + } else { + auto const now_tp = std::chrono::steady_clock::now(); + now = static_cast( + std::chrono::duration_cast( + now_tp.time_since_epoch()) + .count()); + } uint64_t dt { now - last }; float dt_seconds { static_cast(dt) / 1000.0f }; last = now; @@ -554,48 +587,50 @@ auto Application::run() -> void m_key_state_previous = m_key_state; process_libinput_events(); - while (SDL_PollEvent(&e)) { - bool forward_to_imgui { false }; - if (e.type == SDL_EVENT_QUIT) { - m_running = false; - } else if (e.type == SDL_EVENT_WINDOW_RESIZED) { - int width {}, height {}; - SDL_GetWindowSize(m_window, &width, &height); - m_renderer->resize(static_cast(width), - static_cast(height)); - clamp_mouse_to_window(width, height); - forward_to_imgui = true; - } else if (e.type == SDL_EVENT_WINDOW_FOCUS_GAINED) { - m_window_focused = true; - forward_to_imgui = true; - } else if (e.type == SDL_EVENT_WINDOW_FOCUS_LOST) { - m_window_focused = false; - m_ctrl_pressed_count = 0; - m_key_state.fill(false); - m_key_state_previous.fill(false); - m_mouse_dx = 0.0; + if (use_sdl) { + while (SDL_PollEvent(&e)) { + bool forward_to_imgui { false }; + if (e.type == SDL_EVENT_QUIT) { + m_running = false; + } else if (e.type == SDL_EVENT_WINDOW_RESIZED) { + int width {}, height {}; + SDL_GetWindowSize(m_window, &width, &height); + m_renderer->resize(static_cast(width), + static_cast(height)); + clamp_mouse_to_window(width, height); + forward_to_imgui = true; + } else if (e.type == SDL_EVENT_WINDOW_FOCUS_GAINED) { + m_window_focused = true; + forward_to_imgui = true; + } else if (e.type == SDL_EVENT_WINDOW_FOCUS_LOST) { + m_window_focused = false; + m_ctrl_pressed_count = 0; + m_key_state.fill(false); + m_key_state_previous.fill(false); + m_mouse_dx = 0.0; - m_mouse_dy = 0.0; - forward_to_imgui = true; - } else if (e.type == SDL_EVENT_MOUSE_MOTION) { - m_mouse_x = e.motion.x; - m_mouse_y = e.motion.y; - m_mouse_dx = e.motion.xrel; - m_mouse_dy = e.motion.yrel; - forward_to_imgui = true; - } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN - || e.type == SDL_EVENT_MOUSE_BUTTON_UP) { - m_mouse_x = e.button.x; - m_mouse_y = e.button.y; - forward_to_imgui = true; - } else if (e.type == SDL_EVENT_MOUSE_WHEEL) { - m_mouse_x = e.wheel.mouse_x; - m_mouse_y = e.wheel.mouse_y; - forward_to_imgui = true; + m_mouse_dy = 0.0; + forward_to_imgui = true; + } else if (e.type == SDL_EVENT_MOUSE_MOTION) { + m_mouse_x = e.motion.x; + m_mouse_y = e.motion.y; + m_mouse_dx = e.motion.xrel; + m_mouse_dy = e.motion.yrel; + forward_to_imgui = true; + } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN + || e.type == SDL_EVENT_MOUSE_BUTTON_UP) { + m_mouse_x = e.button.x; + m_mouse_y = e.button.y; + forward_to_imgui = true; + } else if (e.type == SDL_EVENT_MOUSE_WHEEL) { + m_mouse_x = e.wheel.mouse_x; + m_mouse_y = e.wheel.mouse_y; + forward_to_imgui = true; + } + + if (forward_to_imgui && use_imgui) + ImGui_ImplSDL3_ProcessEvent(&e); } - - if (forward_to_imgui) - ImGui_ImplSDL3_ProcessEvent(&e); } } @@ -687,7 +722,7 @@ auto Application::run() -> void m_mouse_dy = 0.0; } - { + if (use_imgui) { GZoneScopedN("ImGui"); ImGui_ImplSDL3_NewFrame(); ImGui_ImplVulkan_NewFrame(); @@ -909,14 +944,19 @@ auto Application::init_input() -> void throw std::runtime_error("App init fail"); } - int width {}, height {}; - SDL_GetWindowSize(m_window, &width, &height); - float mouse_x {}, mouse_y {}; - SDL_GetMouseState(&mouse_x, &mouse_y); - m_mouse_x = mouse_x; - m_mouse_y = mouse_y; - ImGui::GetIO().AddMousePosEvent( - static_cast(m_mouse_x), static_cast(m_mouse_y)); + if (m_backend == Backend::SDL) { + int width {}, height {}; + SDL_GetWindowSize(m_window, &width, &height); + float mouse_x {}, mouse_y {}; + SDL_GetMouseState(&mouse_x, &mouse_y); + m_mouse_x = mouse_x; + m_mouse_y = mouse_y; + ImGui::GetIO().AddMousePosEvent( + static_cast(m_mouse_x), static_cast(m_mouse_y)); + } else { + m_mouse_x = 0.0; + m_mouse_y = 0.0; + } } auto Application::shutdown_input() -> void @@ -973,7 +1013,8 @@ auto Application::handle_keyboard_event(libinput_event_keyboard *event) -> void } } - if (pressed && key == KEY_F11 && m_ctrl_pressed_count > 0) { + if (m_backend == Backend::SDL && pressed && key == KEY_F11 + && m_ctrl_pressed_count > 0) { bool const new_show_imgui { !m_show_imgui }; m_show_imgui = new_show_imgui; mouse_captured(!new_show_imgui); @@ -1007,18 +1048,21 @@ auto Application::handle_keyboard_event(libinput_event_keyboard *event) -> void } } - if (auto imgui_key { linux_key_to_imgui(key) }) { - if (m_show_imgui) - ImGui::GetIO().AddKeyEvent(*imgui_key, pressed); - } + if (m_backend == Backend::SDL) { + if (auto imgui_key { linux_key_to_imgui(key) }) { + if (m_show_imgui) + ImGui::GetIO().AddKeyEvent(*imgui_key, pressed); + } - if (m_show_imgui && pressed) { - bool const shift_pressed { is_key_down(KEY_LEFTSHIFT) - || is_key_down(KEY_RIGHTSHIFT) || (key == KEY_LEFTSHIFT && pressed) - || (key == KEY_RIGHTSHIFT && pressed) }; + if (m_show_imgui && pressed) { + bool const shift_pressed { is_key_down(KEY_LEFTSHIFT) + || is_key_down(KEY_RIGHTSHIFT) + || (key == KEY_LEFTSHIFT && pressed) + || (key == KEY_RIGHTSHIFT && pressed) }; - if (auto ch { linux_key_to_char(key, shift_pressed) }) - ImGui::GetIO().AddInputCharacter(*ch); + if (auto ch { linux_key_to_char(key, shift_pressed) }) + ImGui::GetIO().AddInputCharacter(*ch); + } } if (key < m_key_state.size()) @@ -1039,6 +1083,11 @@ auto Application::clamp_mouse_to_window(int width, int height) -> void auto Application::mouse_captured(bool new_state) -> void { + if (m_backend != Backend::SDL) { + m_mouse_captured = false; + return; + } + if (!SDL_SetWindowRelativeMouseMode(m_window, new_state)) { m_logger.err("Failed to capture mouse"); return; diff --git a/src/Application.h b/src/Application.h index 7d0ec45..4e1f3c0 100644 --- a/src/Application.h +++ b/src/Application.h @@ -38,6 +38,11 @@ struct Application { static auto the() -> Application &; private: + enum class Backend { + SDL, + KMS, + }; + Application(); ~Application(); @@ -50,6 +55,7 @@ private: auto clamp_mouse_to_window(int width, int height) -> void; SDL_Window *m_window { nullptr }; + Backend m_backend { Backend::SDL }; Logger m_logger { "Lunar" }; std::unique_ptr m_renderer; Skybox m_skybox; diff --git a/src/VulkanRenderer.cpp b/src/VulkanRenderer.cpp index 61f71a2..a7394f7 100644 --- a/src/VulkanRenderer.cpp +++ b/src/VulkanRenderer.cpp @@ -620,6 +620,9 @@ VulkanRenderer::VulkanRenderer(SDL_Window *window, Logger &logger) throw std::runtime_error("VulkanRenderer requires a valid window"); } + m_use_kms = false; + m_imgui_enabled = true; + vk_init(); swapchain_init(); commands_init(); @@ -630,6 +633,22 @@ VulkanRenderer::VulkanRenderer(SDL_Window *window, Logger &logger) imgui_init(); } +VulkanRenderer::VulkanRenderer(KmsSurfaceConfig /*config*/, Logger &logger) + : gl(*this) + , m_logger(logger) +{ + m_use_kms = true; + m_imgui_enabled = false; + + vk_init(); + swapchain_init(); + commands_init(); + sync_init(); + descriptors_init(); + pipelines_init(); + default_data_init(); +} + VulkanRenderer::~VulkanRenderer() { m_device.waitIdle(); @@ -671,8 +690,12 @@ VulkanRenderer::~VulkanRenderer() } if (m_vk.surface) { - SDL_Vulkan_DestroySurface( - m_vkb.instance, static_cast(m_vk.surface), nullptr); + if (m_use_kms) { + m_instance.destroySurfaceKHR(m_vk.surface); + } else { + SDL_Vulkan_DestroySurface(m_vkb.instance, + static_cast(m_vk.surface), nullptr); + } m_vk.surface = nullptr; } @@ -847,6 +870,107 @@ auto VulkanRenderer::immediate_submit( } } +auto VulkanRenderer::setup_kms_surface() -> void +{ + auto const devices = m_instance.enumeratePhysicalDevices(); + if (devices.empty()) { + m_logger.err("No Vulkan physical devices available for KMS"); + throw std::runtime_error("App init fail"); + } + + for (auto const &device : devices) { + auto const displays = device.getDisplayPropertiesKHR(); + if (displays.empty()) { + continue; + } + + for (auto const &display_props : displays) { + auto const modes + = device.getDisplayModePropertiesKHR(display_props.display); + if (modes.empty()) { + continue; + } + + auto const best_mode_it = std::max_element(modes.begin(), + modes.end(), [](auto const &lhs, auto const &rhs) { + auto const lhs_extent = lhs.parameters.visibleRegion; + auto const rhs_extent = rhs.parameters.visibleRegion; + auto const lhs_area + = static_cast(lhs_extent.width) + * static_cast(lhs_extent.height); + auto const rhs_area + = static_cast(rhs_extent.width) + * static_cast(rhs_extent.height); + if (lhs_area == rhs_area) { + return lhs.parameters.refreshRate + < rhs.parameters.refreshRate; + } + return lhs_area < rhs_area; + }); + + auto const planes = device.getDisplayPlanePropertiesKHR(); + std::optional plane_index; + uint32_t plane_stack_index { 0 }; + for (uint32_t i = 0; i < planes.size(); ++i) { + auto const supported_displays + = device.getDisplayPlaneSupportedDisplaysKHR(i); + if (std::find(supported_displays.begin(), + supported_displays.end(), display_props.display) + != supported_displays.end()) { + plane_index = i; + plane_stack_index = planes[i].currentStackIndex; + break; + } + } + if (!plane_index) { + continue; + } + + auto const extent = best_mode_it->parameters.visibleRegion; + KmsState state {}; + state.display = display_props.display; + state.mode = best_mode_it->displayMode; + state.extent = extent; + state.plane_index = *plane_index; + state.plane_stack_index = plane_stack_index; + if (display_props.displayName) { + state.display_name = display_props.displayName; + } + + m_kms_state = state; + m_kms_extent = extent; + m_kms_physical_device = device; + m_kms_physical_device_set = true; + + m_logger.info("Using KMS display {} ({}x{} @ {} mHz)", + state.display_name.empty() ? "unnamed" : state.display_name, + extent.width, extent.height, + best_mode_it->parameters.refreshRate); + break; + } + + if (m_kms_state) { + break; + } + } + + if (!m_kms_state) { + m_logger.err("No suitable KMS display found"); + throw std::runtime_error("App init fail"); + } + + vk::DisplaySurfaceCreateInfoKHR surface_info {}; + surface_info.displayMode = m_kms_state->mode; + surface_info.planeIndex = m_kms_state->plane_index; + surface_info.planeStackIndex = m_kms_state->plane_stack_index; + surface_info.transform = vk::SurfaceTransformFlagBitsKHR::eIdentity; + surface_info.alphaMode = vk::DisplayPlaneAlphaFlagBitsKHR::eOpaque; + surface_info.globalAlpha = 1.0f; + surface_info.imageExtent = m_kms_state->extent; + + m_vk.surface = m_instance.createDisplayPlaneSurfaceKHR(surface_info); +} + auto VulkanRenderer::vk_init() -> void { VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); @@ -884,6 +1008,9 @@ auto VulkanRenderer::vk_init() -> void return VK_FALSE; }); + if (m_use_kms) { + instance_builder.enable_extension(VK_KHR_DISPLAY_EXTENSION_NAME); + } #ifndef NDEBUG instance_builder.request_validation_layers(); #endif @@ -898,13 +1025,17 @@ auto VulkanRenderer::vk_init() -> void m_instance = vk::Instance { m_vkb.instance.instance }; VULKAN_HPP_DEFAULT_DISPATCHER.init(m_instance); - VkSurfaceKHR raw_surface {}; - if (!SDL_Vulkan_CreateSurface( - m_window, m_vkb.instance, nullptr, &raw_surface)) { - m_logger.err("Failed to create vulkan surface"); - throw std::runtime_error("App init fail"); + if (m_use_kms) { + setup_kms_surface(); + } else { + VkSurfaceKHR raw_surface {}; + if (!SDL_Vulkan_CreateSurface( + m_window, m_vkb.instance, nullptr, &raw_surface)) { + m_logger.err("Failed to create vulkan surface"); + throw std::runtime_error("App init fail"); + } + m_vk.surface = vk::SurfaceKHR { raw_surface }; } - m_vk.surface = vk::SurfaceKHR { raw_surface }; vkb::PhysicalDeviceSelector phys_device_selector { m_vkb.instance }; VkPhysicalDeviceVulkan13Features features_13 {}; @@ -946,6 +1077,10 @@ auto VulkanRenderer::vk_init() -> void m_logger.info("Chosen Vulkan physical device: {}", m_vkb.phys_dev.properties.deviceName); + if (m_use_kms && m_kms_physical_device_set + && m_physical_device != m_kms_physical_device) { + m_logger.warn("KMS display is not on the selected physical device"); + } auto const props = m_physical_device.getProperties(); m_vk.supported_framebuffer_samples @@ -974,6 +1109,13 @@ auto VulkanRenderer::vk_init() -> void } m_vk.graphics_queue_family = queue_family_ret.value(); m_vk.graphics_queue = m_device.getQueue(m_vk.graphics_queue_family, 0); + if (m_use_kms) { + if (!m_physical_device.getSurfaceSupportKHR( + m_vk.graphics_queue_family, m_vk.surface)) { + m_logger.err("Selected device does not support KMS surface"); + throw std::runtime_error("App init fail"); + } + } VmaAllocatorCreateInfo allocator_ci {}; allocator_ci.physicalDevice = m_vkb.phys_dev.physical_device; @@ -985,12 +1127,21 @@ auto VulkanRenderer::vk_init() -> void auto VulkanRenderer::swapchain_init() -> void { - int w, h; - SDL_GetWindowSize(m_window, &w, &h); - create_swapchain(static_cast(w), static_cast(h)); - create_draw_image(static_cast(w), static_cast(h)); - create_msaa_color_image(static_cast(w), static_cast(h)); - create_depth_image(static_cast(w), static_cast(h)); + uint32_t width { 0 }; + uint32_t height { 0 }; + if (m_use_kms) { + width = m_kms_extent.width; + height = m_kms_extent.height; + } else { + int w {}, h {}; + SDL_GetWindowSize(m_window, &w, &h); + width = static_cast(w); + height = static_cast(h); + } + create_swapchain(width, height); + create_draw_image(width, height); + create_msaa_color_image(width, height); + create_depth_image(width, height); } auto VulkanRenderer::commands_init() -> void @@ -1404,10 +1555,14 @@ auto VulkanRenderer::render(std::function const &record) -> void m_vk.swapchain, 1'000'000'000, frame.swapchain_semaphore.get(), {}); if (acquire_result.result == vk::Result::eErrorOutOfDateKHR || acquire_result.result == vk::Result::eSuboptimalKHR) { - int width {}, height {}; - SDL_GetWindowSize(m_window, &width, &height); - recreate_swapchain( - static_cast(width), static_cast(height)); + if (m_use_kms) { + recreate_swapchain(m_kms_extent.width, m_kms_extent.height); + } else { + int width {}, height {}; + SDL_GetWindowSize(m_window, &width, &height); + recreate_swapchain( + static_cast(width), static_cast(height)); + } return; } VK_CHECK(m_logger, acquire_result.result); @@ -1463,7 +1618,10 @@ auto VulkanRenderer::render(std::function const &record) -> void vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eColorAttachmentOptimal); - draw_imgui(cmd, m_vk.swapchain_image_views.at(swapchain_image_idx).get()); + if (m_imgui_enabled) { + draw_imgui( + cmd, m_vk.swapchain_image_views.at(swapchain_image_idx).get()); + } vkutil::transition_image(cmd, m_vk.swapchain_images.at(swapchain_image_idx), vk::ImageLayout::eColorAttachmentOptimal, @@ -1577,10 +1735,14 @@ auto VulkanRenderer::render(std::function const &record) -> void auto const present_result = m_vk.graphics_queue.presentKHR(present_info); if (present_result == vk::Result::eErrorOutOfDateKHR || present_result == vk::Result::eSuboptimalKHR) { - int width {}, height {}; - SDL_GetWindowSize(m_window, &width, &height); - recreate_swapchain( - static_cast(width), static_cast(height)); + if (m_use_kms) { + recreate_swapchain(m_kms_extent.width, m_kms_extent.height); + } else { + int width {}, height {}; + SDL_GetWindowSize(m_window, &width, &height); + recreate_swapchain( + static_cast(width), static_cast(height)); + } return; } VK_CHECK(m_logger, present_result); diff --git a/src/VulkanRenderer.h b/src/VulkanRenderer.h index baf0d5e..b24937a 100644 --- a/src/VulkanRenderer.h +++ b/src/VulkanRenderer.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -130,7 +131,10 @@ struct VulkanRenderer { MSAA_8X, }; + struct KmsSurfaceConfig { }; + VulkanRenderer(SDL_Window *window, Logger &logger); + VulkanRenderer(KmsSurfaceConfig config, Logger &logger); ~VulkanRenderer(); auto render(std::function const &record = {}) -> void; @@ -232,6 +236,7 @@ private: auto vk_init() -> void; auto swapchain_init() -> void; + auto setup_kms_surface() -> void; auto commands_init() -> void; auto sync_init() -> void; auto descriptors_init() -> void; @@ -359,10 +364,25 @@ private: vk::UniqueSampler default_sampler_nearest; } m_vk; + struct KmsState { + vk::DisplayKHR display {}; + vk::DisplayModeKHR mode {}; + vk::Extent2D extent {}; + uint32_t plane_index { 0 }; + uint32_t plane_stack_index { 0 }; + std::string display_name {}; + }; + SDL_Window *m_window { nullptr }; Logger &m_logger; std::mutex m_command_mutex; std::vector m_pending_render_commands; + bool m_use_kms { false }; + bool m_imgui_enabled { true }; + std::optional m_kms_state {}; + vk::PhysicalDevice m_kms_physical_device {}; + vk::Extent2D m_kms_extent {}; + bool m_kms_physical_device_set { false }; }; } // namespace Lunar