diff --git a/.clangd b/.clangd index 8647ae2..39b4547 100644 --- a/.clangd +++ b/.clangd @@ -1,5 +1,6 @@ CompileFlags: Add: [ "-isystem", "/nix/store/9hy16b0ba5bz0gd309rvhh119mn44f4j-libcxx-21.1.2-dev/include/c++/v1", - "-isystem", "/nix/store/jkqdwnd0ifr3fkjif3lz003an2fhbh17-glibc-2.40-66-dev/include" + "-isystem", "/nix/store/jkqdwnd0ifr3fkjif3lz003an2fhbh17-glibc-2.40-66-dev/include", + "-I/nix/store/sqkna0r9zvxi7qdf8n6qckhibp1z2422-vulkan-utility-libraries-1.4.328.0/include" ] diff --git a/flake.nix b/flake.nix index 39a1ba7..fdf3f61 100644 --- a/flake.nix +++ b/flake.nix @@ -17,20 +17,22 @@ let pkgs = import nixpkgs { inherit system; }; nativeBuildInputs = with pkgs; [ + cmake + meson + ninja + pkg-config + ]; + buildInputs = with pkgs; [ vulkan-loader vulkan-memory-allocator vulkan-validation-layers + vulkan-utility-libraries vk-bootstrap openxr-loader wayland zlib sdl3 ]; - buildInputs = with pkgs; [ - meson - ninja - pkg-config - ]; in { devShells.default = @@ -62,7 +64,8 @@ CompileFlags: Add: [ "-isystem", "${pkgs.llvmPackages_21.libcxx.dev}/include/c++/v1", - "-isystem", "${pkgs.glibc.dev}/include" + "-isystem", "${pkgs.glibc.dev}/include", + "-I${pkgs.vulkan-utility-libraries}/include" ] EOF ''; diff --git a/meson.build b/meson.build index a18194e..1f8f142 100644 --- a/meson.build +++ b/meson.build @@ -35,6 +35,7 @@ add_project_arguments( exe = executable('vr-compositor', [ 'src/main.cpp', + 'src/Util.cpp', 'src/Logger.cpp', 'src/Application.cpp', ], diff --git a/src/Application.cpp b/src/Application.cpp index 205b019..74d33e5 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -1,19 +1,48 @@ #include "Application.h" -#include +#include #include #include +#include +#include #include #include #include +#include "Util.h" +#include "vulkan/vulkan_core.h" + 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"); + } + + 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"); + } + + vk_init(); + swapchain_init(); + commands_init(); + sync_init(); + + m_logger.info("App init done!"); +} + +auto Application::vk_init() -> void { vkb::InstanceBuilder instance_builder {}; - instance_builder.request_validation_layers() + instance_builder + .enable_extension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME) + .request_validation_layers() .set_app_name("Lunar") .set_engine_name("Lunar") .require_api_version(1, 0, 0) @@ -51,51 +80,279 @@ Application::Application() throw std::runtime_error("App init fail"); } - m_vkb_instance = instance_builder_ret.value(); - - 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_vkb.instance = instance_builder_ret.value(); if (!SDL_Vulkan_CreateSurface( - m_window, m_vkb_instance, nullptr, &m_vk_surface)) { + m_window, m_vkb.instance, nullptr, &m_vk.surface)) { m_logger.err("Failed to create vulkan surface"); throw std::runtime_error("App init fail"); } - vkb::PhysicalDeviceSelector phys_device_selector { m_vkb_instance }; - phys_device_selector.set_surface(m_vk_surface) - .add_required_extensions({ VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, + vkb::PhysicalDeviceSelector phys_device_selector { m_vkb.instance }; + phys_device_selector.set_surface(m_vk.surface) + .add_desired_extensions({ + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, - VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME }); + VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, + VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, + VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, + VK_KHR_MAINTENANCE1_EXTENSION_NAME, + VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, + }); auto physical_device_selector_return { phys_device_selector.select() }; if (!physical_device_selector_return) { - std::println(std::cerr, "Failed to find Vulkan device. Error: {}", + std::println(std::cerr, + "Failed to find Vulkan physical device. Error: {}", physical_device_selector_return.error().message()); throw std::runtime_error("App init fail"); } - auto phys_device = physical_device_selector_return.value(); + m_vkb.phys_dev = physical_device_selector_return.value(); - m_logger.info("App init done!"); + vkb::DeviceBuilder device_builder { m_vkb.phys_dev }; + auto dev_ret { device_builder.build() }; + if (!dev_ret) { + std::println(std::cerr, "Failed to create Vulkan device. Error: {}", + dev_ret.error().message()); + throw std::runtime_error("App init fail"); + } + m_vkb.dev = dev_ret.value(); + + auto queue_ret { m_vkb.dev.get_queue(vkb::QueueType::graphics) }; + if (!queue_ret) { + std::println(std::cerr, "Failed to get graphics queue. Error: {}", + queue_ret.error().message()); + throw std::runtime_error("App init fail"); + } + m_vk.graphics_queue = queue_ret.value(); + + auto queue_family_ret { m_vkb.dev.get_queue_index( + vkb::QueueType::graphics) }; + if (!queue_family_ret) { + std::println(std::cerr, "Failed to get graphics queue. Error: {}", + queue_family_ret.error().message()); + throw std::runtime_error("App init fail"); + } + m_vk.graphics_queue_family = queue_family_ret.value(); +} + +auto Application::swapchain_init() -> void +{ + int w, h; + SDL_GetWindowSize(m_window, &w, &h); + create_swapchain(w, h); +} + +auto Application::commands_init() -> void +{ + VkCommandPoolCreateInfo ci { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = m_vk.graphics_queue_family, + }; + for (unsigned i = 0; i < FRAME_OVERLAP; i++) { + VK_CHECK(m_logger, + vkCreateCommandPool( + m_vkb.dev, &ci, nullptr, &m_vk.frames.at(i).command_pool)); + + VkCommandBufferAllocateInfo ai { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = nullptr, + .commandPool = m_vk.frames.at(i).command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + + VK_CHECK(m_logger, + vkAllocateCommandBuffers( + m_vkb.dev, &ai, &m_vk.frames.at(i).main_command_buffer)); + } +} + +auto Application::sync_init() -> void +{ + VkFenceCreateInfo fence_ci { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .pNext = nullptr, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }; + VkSemaphoreCreateInfo semaphore_ci { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + }; + for (auto &frame_data : m_vk.frames) { + VK_CHECK(m_logger, + vkCreateFence( + m_vkb.dev, &fence_ci, nullptr, &frame_data.render_fence)); + + VK_CHECK(m_logger, + vkCreateSemaphore(m_vkb.dev, &semaphore_ci, nullptr, + &frame_data.swapchain_semaphore)); + } +} + +auto Application::render() -> void +{ + defer(m_vk.frame_number++); + + VK_CHECK(m_logger, + vkWaitForFences(m_vkb.dev, 1, &m_vk.get_current_frame().render_fence, + true, 1'000'000'000)); + VK_CHECK(m_logger, + vkResetFences(m_vkb.dev, 1, &m_vk.get_current_frame().render_fence)); + + uint32_t swapchain_image_idx; + VK_CHECK(m_logger, + vkAcquireNextImageKHR(m_vkb.dev, m_vk.swapchain, 1000000000, + m_vk.get_current_frame().swapchain_semaphore, nullptr, + &swapchain_image_idx)); + + auto cmd { m_vk.get_current_frame().main_command_buffer }; + VK_CHECK(m_logger, vkResetCommandBuffer(cmd, 0)); + VkCommandBufferBeginInfo cmd_begin_info { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = nullptr, + }; + VK_CHECK(m_logger, vkBeginCommandBuffer(cmd, &cmd_begin_info)); + + vkutil::transition_image(cmd, m_vk.swapchain_images.at(swapchain_image_idx), + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); + + VkClearColorValue clear_value; + float flash { std::abs(std::sin(m_vk.frame_number / 60.f)) }; + clear_value = { { 0x64 / 255.0f * flash, 0x95 / 255.0f * flash, + 0xED / 255.0f * flash, 1.0f } }; + + VkImageSubresourceRange clear_range { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + vkCmdClearColorImage(cmd, m_vk.swapchain_images[swapchain_image_idx], + VK_IMAGE_LAYOUT_GENERAL, &clear_value, 1, &clear_range); + + vkutil::transition_image(cmd, m_vk.swapchain_images[swapchain_image_idx], + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + VK_CHECK(m_logger, vkEndCommandBuffer(cmd)); + + VkSemaphore render_semaphore + = m_vk.present_semaphores.at(swapchain_image_idx); + VkPipelineStageFlags wait_stage + = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkSubmitInfo submit_info { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = nullptr, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &m_vk.get_current_frame().swapchain_semaphore, + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 1, + .pCommandBuffers = &cmd, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &render_semaphore, + }; + + VK_CHECK(m_logger, + vkQueueSubmit(m_vk.graphics_queue, 1, &submit_info, + m_vk.get_current_frame().render_fence)); + + VkPresentInfoKHR present_info = {}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.pNext = nullptr; + present_info.pSwapchains = &m_vk.swapchain; + present_info.swapchainCount = 1; + + present_info.pWaitSemaphores = &render_semaphore; + present_info.waitSemaphoreCount = 1; + + present_info.pImageIndices = &swapchain_image_idx; + + VK_CHECK(m_logger, vkQueuePresentKHR(m_vk.graphics_queue, &present_info)); +} + +auto Application::create_swapchain(uint32_t width, uint32_t height) -> void +{ + vkb::SwapchainBuilder builder { m_vkb.phys_dev, m_vkb.dev, m_vk.surface }; + m_vk.swapchain_image_format = VK_FORMAT_B8G8R8A8_UNORM; + auto const swapchain_ret { builder + .set_desired_format({ + .format = m_vk.swapchain_image_format, + .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + }) + .set_desired_present_mode(VK_PRESENT_MODE_FIFO_KHR) + .set_desired_extent(width, height) + .add_image_usage_flags(VK_IMAGE_USAGE_TRANSFER_DST_BIT) + .build() }; + if (!swapchain_ret) { + std::println(std::cerr, "Failed to create swapchain. Error: {}", + swapchain_ret.error().message()); + throw std::runtime_error("App init fail"); + } + m_vkb.swapchain = swapchain_ret.value(); + + m_vk.swapchain = m_vkb.swapchain.swapchain; + m_vk.swapchain_extent = m_vkb.swapchain.extent; + m_vk.swapchain_images = m_vkb.swapchain.get_images().value(); + m_vk.swapchain_image_views = m_vkb.swapchain.get_image_views().value(); + + VkSemaphoreCreateInfo semaphore_ci { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + }; + m_vk.present_semaphores.resize(m_vk.swapchain_images.size()); + for (auto &semaphore : m_vk.present_semaphores) { + VK_CHECK(m_logger, + vkCreateSemaphore(m_vkb.dev, &semaphore_ci, nullptr, &semaphore)); + } +} + +auto Application::destroy_swapchain() -> void +{ + if (m_vk.swapchain == VK_NULL_HANDLE) + return; + + for (auto const semaphore : m_vk.present_semaphores) { + vkDestroySemaphore(m_vkb.dev, semaphore, nullptr); + } + for (auto const &iv : m_vk.swapchain_image_views) { + vkDestroyImageView(m_vkb.dev, iv, nullptr); + } + vkDestroySwapchainKHR(m_vkb.dev, m_vk.swapchain, nullptr); + + m_vk.swapchain = VK_NULL_HANDLE; + m_vk.swapchain_image_views.clear(); + m_vk.swapchain_images.clear(); + m_vk.present_semaphores.clear(); } Application::~Application() { - SDL_Vulkan_DestroySurface(m_vkb_instance, m_vk_surface, nullptr); + vkDeviceWaitIdle(m_vkb.dev); + + for (auto &frame_data : m_vk.frames) { + vkDestroyCommandPool(m_vkb.dev, frame_data.command_pool, nullptr); + + vkDestroyFence(m_vkb.dev, frame_data.render_fence, nullptr); + vkDestroySemaphore(m_vkb.dev, frame_data.swapchain_semaphore, nullptr); + } + + destroy_swapchain(); + + SDL_Vulkan_DestroySurface(m_vkb.instance, m_vk.surface, nullptr); SDL_DestroyWindow(m_window); SDL_Quit(); - vkb::destroy_instance(m_vkb_instance); + vkb::destroy_device(m_vkb.dev); + vkb::destroy_instance(m_vkb.instance); m_logger.info("App destroy done!"); } @@ -109,7 +366,8 @@ auto Application::run() -> void if (e.type == SDL_EVENT_QUIT) m_running = false; } - // nothing else + + render(); } } diff --git a/src/Application.h b/src/Application.h index 28e8427..e9a7233 100644 --- a/src/Application.h +++ b/src/Application.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -8,6 +10,15 @@ namespace Lunar { +struct FrameData { + VkCommandPool command_pool; + VkCommandBuffer main_command_buffer; + VkSemaphore swapchain_semaphore; + VkFence render_fence; +}; + +constexpr unsigned FRAME_OVERLAP = 2; + struct Application { Application(); ~Application(); @@ -15,8 +26,45 @@ struct Application { auto run() -> void; private: - vkb::Instance m_vkb_instance; - VkSurfaceKHR m_vk_surface { nullptr }; + auto vk_init() -> void; + auto swapchain_init() -> void; + auto commands_init() -> void; + auto sync_init() -> void; + + auto render() -> void; + + auto create_swapchain(uint32_t width, uint32_t height) -> void; + auto destroy_swapchain() -> void; + + struct { + vkb::Instance instance; + vkb::PhysicalDevice phys_dev; + vkb::Device dev; + vkb::Swapchain swapchain; + } m_vkb; + + struct { + VkSwapchainKHR swapchain { VK_NULL_HANDLE }; + VkFormat swapchain_image_format; + VkSurfaceKHR surface { nullptr }; + + VkQueue graphics_queue { nullptr }; + uint32_t graphics_queue_family { 0 }; + + std::vector swapchain_images; + std::vector swapchain_image_views; + std::vector present_semaphores; + VkExtent2D swapchain_extent; + + std::array frames; + auto get_current_frame() -> FrameData & + { + return frames.at(frame_number % frames.size()); + } + + uint64_t frame_number { 0 }; + } m_vk; + SDL_Window *m_window { nullptr }; Logger m_logger { "Lunar" }; diff --git a/src/Util.cpp b/src/Util.cpp new file mode 100644 index 0000000..b0c9e6a --- /dev/null +++ b/src/Util.cpp @@ -0,0 +1,40 @@ +#include "Util.h" + +namespace vkutil { + +void transition_image(VkCommandBuffer cmd, VkImage image, + VkImageLayout current_layout, VkImageLayout new_layout) +{ + VkImageAspectFlags aspect_mask + = (new_layout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL) + ? VK_IMAGE_ASPECT_DEPTH_BIT + : VK_IMAGE_ASPECT_COLOR_BIT; + + VkImageMemoryBarrier image_barrier { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask + = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT, + .oldLayout = current_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = { + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + vkCmdPipelineBarrier(cmd, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, + &image_barrier); +} + +} // namespace vkutil diff --git a/src/Util.h b/src/Util.h index 8a150f6..9a4155f 100644 --- a/src/Util.h +++ b/src/Util.h @@ -1,6 +1,10 @@ #pragma once #include + +#include +#include + using namespace std::string_view_literals; template struct privDefer { @@ -24,3 +28,19 @@ template privDefer defer_func(F f) { return privDefer(f); } #else # define ALIGN(a) __attribute__((aligned(a))) #endif + +#define VK_CHECK(logger, x) \ + do { \ + VkResult err { x }; \ + if (err) { \ + (logger).err("Detected Vulkan error: {}", string_VkResult(err)); \ + throw std::runtime_error("Vulkan error"); \ + } \ + } while (0) + +namespace vkutil { + +void transition_image(VkCommandBuffer cmd, VkImage image, + VkImageLayout current_layout, VkImageLayout new_layout); + +} // namespace vkutil