diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a10293..d487f54 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ add_library(lsfg-vk SHARED ${SOURCES}) target_include_directories(lsfg-vk PRIVATE include) target_link_libraries(lsfg-vk - PRIVATE lsfg-vk-gen) + PRIVATE lsfg-vk-gen vulkan) target_compile_options(lsfg-vk PRIVATE -Weverything # disable compat c++ flags diff --git a/include/application.hpp b/include/application.hpp index 16f4d0a..60bad94 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -1,14 +1,18 @@ #ifndef APPLICATION_HPP #define APPLICATION_HPP +#include +#include + #include +class SwapchainContext; + /// /// Main application class, wrapping around the Vulkan device. /// class Application { public: - /// /// Create the application. /// @@ -20,6 +24,31 @@ public: /// Application(VkDevice device, VkPhysicalDevice physicalDevice); + /// + /// Add a swapchain to the application. + /// + /// @param handle The Vulkan handle of the swapchain. + /// @param format The format of the swapchain. + /// @param extent The extent of the images. + /// @param images The swapchain images. + /// + /// @throws std::invalid_argument if the handle is already added. + /// @throws ls::vulkan_error if any Vulkan call fails. + /// + void addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, + const std::vector& images); + + /// + /// Remove a swapchain from the application. + /// + /// @param handle The Vulkan handle of the swapchain state to remove. + /// @return true if the swapchain was removed, false if it was already retired. + /// + /// @throws std::invalid_argument if the handle is null. + /// + bool removeSwapchain(VkSwapchainKHR handle); + + /// Get the Vulkan device. [[nodiscard]] VkDevice getDevice() const { return this->device; } /// Get the Vulkan physical device. @@ -39,7 +68,52 @@ private: VkPhysicalDevice physicalDevice; // (owned resources) + std::unordered_map swapchains; +}; +/// +/// An application's Vulkan swapchain and it's associated resources. +/// +class SwapchainContext { +public: + + /// + /// Create the swapchain context. + /// + /// @param swapchain The Vulkan swapchain handle. + /// @param format The format of the swapchain images. + /// @param extent The extent of the swapchain images. + /// @param images The swapchain images. + /// + /// @throws std::invalid_argument if any parameter is null + /// @throws ls::vulkan_error if any Vulkan call fails. + /// + SwapchainContext(VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, + const std::vector& images); + + /// Get the Vulkan swapchain handle. + [[nodiscard]] VkSwapchainKHR handle() const { return this->swapchain; } + /// Get the format of the swapchain images. + [[nodiscard]] VkFormat getFormat() const { return this->format; } + /// Get the extent of the swapchain images. + [[nodiscard]] VkExtent2D getExtent() const { return this->extent; } + /// Get the swapchain images. + [[nodiscard]] const std::vector& getImages() const { return this->images; } + + // Non-copyable, trivially moveable and destructible + SwapchainContext(const SwapchainContext&) = delete; + SwapchainContext& operator=(const SwapchainContext&) = delete; + SwapchainContext(SwapchainContext&&) = default; + SwapchainContext& operator=(SwapchainContext&&) = default; + ~SwapchainContext() = default; +private: + // (non-owned resources) + VkSwapchainKHR swapchain{}; + VkFormat format{}; + VkExtent2D extent{}; + std::vector images; + + // (owned resources) }; #endif // APPLICATION_HPP diff --git a/include/vulkan/funcs.hpp b/include/vulkan/funcs.hpp index 0ad93d6..a91d07c 100644 --- a/include/vulkan/funcs.hpp +++ b/include/vulkan/funcs.hpp @@ -48,6 +48,20 @@ namespace Vulkan::Funcs { VkDevice device, const VkAllocationCallbacks* pAllocator ); + + /// Call to the original vkCreateSwapchainKHR function. + VkResult ovkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain + ); + /// Call to the original vkDestroySwapchainKHR function. + void ovkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator + ); } #endif // FUNCS_HPP diff --git a/src/application.cpp b/src/application.cpp index b22b10c..7f06f81 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -7,3 +7,33 @@ Application::Application(VkDevice device, VkPhysicalDevice physicalDevice) if (device == VK_NULL_HANDLE || physicalDevice == VK_NULL_HANDLE) throw std::invalid_argument("Invalid Vulkan device or physical device"); } + +void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, + const std::vector& images) { + // add the swapchain handle + auto it = this->swapchains.find(handle); + if (it != this->swapchains.end()) + throw std::invalid_argument("Swapchain handle already exists"); + + // initialize the swapchain context + this->swapchains.emplace(handle, SwapchainContext(handle, format, extent, images)); +} + +SwapchainContext::SwapchainContext(VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, + const std::vector& images) + : swapchain(swapchain), format(format), extent(extent), images(images) { + if (swapchain == VK_NULL_HANDLE || format == VK_FORMAT_UNDEFINED) + throw std::invalid_argument("Invalid swapchain or images"); +} + +bool Application::removeSwapchain(VkSwapchainKHR handle) { + if (handle == VK_NULL_HANDLE) + throw std::invalid_argument("Invalid swapchain handle"); + + // remove the swapchain context + auto it = this->swapchains.find(handle); + if (it == this->swapchains.end()) + return false; // already retired... hopefully + this->swapchains.erase(it); + return true; +} diff --git a/src/vulkan/funcs.cpp b/src/vulkan/funcs.cpp index 51a74bd..226f602 100644 --- a/src/vulkan/funcs.cpp +++ b/src/vulkan/funcs.cpp @@ -2,6 +2,7 @@ #include "loader/dl.hpp" #include "loader/vk.hpp" #include "log.hpp" +#include using namespace Vulkan; @@ -11,6 +12,9 @@ namespace { PFN_vkCreateDevice vkCreateDevice_ptr{}; PFN_vkDestroyDevice vkDestroyDevice_ptr{}; + + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR_ptr{}; + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR_ptr{}; } void Funcs::initialize() { @@ -51,6 +55,20 @@ void Funcs::initializeInstance(VkInstance instance) { } void Funcs::initializeDevice(VkDevice device) { + if (vkCreateSwapchainKHR_ptr || vkDestroySwapchainKHR_ptr) { + Log::warn("lsfg-vk(funcs): Device Vulkan functions already initialized, did you call it twice?"); + return; + } + + vkCreateSwapchainKHR_ptr = reinterpret_cast( + Loader::VK::ovkGetDeviceProcAddr(device, "vkCreateSwapchainKHR")); + vkDestroySwapchainKHR_ptr = reinterpret_cast( + Loader::VK::ovkGetDeviceProcAddr(device, "vkDestroySwapchainKHR")); + if (!vkCreateSwapchainKHR_ptr || !vkDestroySwapchainKHR_ptr) { + Log::error("lsfg-vk(funcs): Failed to initialize Vulkan device functions, missing symbols"); + exit(EXIT_FAILURE); + } + Log::debug("lsfg-vk(funcs): Initialized device Vulkan functions with original pointers"); } @@ -82,3 +100,18 @@ void Funcs::ovkDestroyDevice( const VkAllocationCallbacks* pAllocator) { vkDestroyDevice_ptr(device, pAllocator); } + +VkResult Funcs::ovkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) { + return vkCreateSwapchainKHR_ptr(device, pCreateInfo, pAllocator, pSwapchain); +} + +void Funcs::ovkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) { + vkDestroySwapchainKHR_ptr(device, swapchain, pAllocator); +} diff --git a/src/vulkan/hooks.cpp b/src/vulkan/hooks.cpp index e711fca..5799fe4 100644 --- a/src/vulkan/hooks.cpp +++ b/src/vulkan/hooks.cpp @@ -56,6 +56,73 @@ namespace { return res; } + VkResult myvkCreateSwapchainKHR( + VkDevice device, + const VkSwapchainCreateInfoKHR* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkSwapchainKHR* pSwapchain) { + auto res = Funcs::ovkCreateSwapchainKHR(device, pCreateInfo, pAllocator, pSwapchain); + + // add the swapchain to the application + if (!application.has_value()) { + Log::error("Application not initialized, cannot create swapchain"); + exit(EXIT_FAILURE); + } + + try { + if (pCreateInfo->oldSwapchain) { + if (!application->removeSwapchain(pCreateInfo->oldSwapchain)) + throw std::runtime_error("Failed to remove old swapchain"); + Log::info("lsfg-vk(hooks): Swapchain retired successfully"); + } + + uint32_t imageCount{}; + auto res = vkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, nullptr); + if (res != VK_SUCCESS || imageCount == 0) + throw LSFG::vulkan_error(res, "Failed to get swapchain images count"); + + std::vector swapchainImages(imageCount); + res = vkGetSwapchainImagesKHR(device, *pSwapchain, &imageCount, swapchainImages.data()); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to get swapchain images"); + + application->addSwapchain(*pSwapchain, + pCreateInfo->imageFormat, pCreateInfo->imageExtent, swapchainImages); + Log::info("lsfg-vk(hooks): Swapchain created successfully with {} images", + swapchainImages.size()); + } catch (const LSFG::vulkan_error& e) { + Log::error("Encountered Vulkan error {:x} while creating swapchain: {}", + static_cast(e.error()), e.what()); + exit(EXIT_FAILURE); + } catch (const std::exception& e) { + Log::error("Encountered error while creating swapchain: {}", e.what()); + exit(EXIT_FAILURE); + } + + return res; + } + + void myvkDestroySwapchainKHR( + VkDevice device, + VkSwapchainKHR swapchain, + const VkAllocationCallbacks* pAllocator) { + if (!application.has_value()) { + Log::error("Application not initialized, cannot destroy swapchain"); + exit(EXIT_FAILURE); + } + + // remove the swapchain from the application + try { + if (application->removeSwapchain(swapchain)) + Log::info("lsfg-vk(hooks): Swapchain retired successfully"); + } catch (const std::exception& e) { + Log::error("Encountered error while removing swapchain: {}", e.what()); + exit(EXIT_FAILURE); + } + + Funcs::ovkDestroySwapchainKHR(device, swapchain, pAllocator); + } + void myvkDestroyDevice( VkDevice device, const VkAllocationCallbacks* pAllocator) { @@ -85,6 +152,10 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateDevice)); Loader::VK::registerSymbol("vkDestroyDevice", reinterpret_cast(myvkDestroyDevice)); + Loader::VK::registerSymbol("vkCreateSwapchainKHR", + reinterpret_cast(myvkCreateSwapchainKHR)); + Loader::VK::registerSymbol("vkDestroySwapchainKHR", + reinterpret_cast(myvkDestroySwapchainKHR)); // register hooks to dynamic loader under libvulkan.so.1 Loader::DL::File vk1("libvulkan.so.1"); @@ -94,6 +165,10 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateDevice)); vk1.defineSymbol("vkDestroyDevice", reinterpret_cast(myvkDestroyDevice)); + vk1.defineSymbol("vkCreateSwapchainKHR", + reinterpret_cast(myvkCreateSwapchainKHR)); + vk1.defineSymbol("vkDestroySwapchainKHR", + reinterpret_cast(myvkDestroySwapchainKHR)); Loader::DL::registerFile(vk1); // register hooks to dynamic loader under libvulkan.so @@ -104,5 +179,9 @@ void Hooks::initialize() { reinterpret_cast(myvkCreateDevice)); vk2.defineSymbol("vkDestroyDevice", reinterpret_cast(myvkDestroyDevice)); + vk2.defineSymbol("vkCreateSwapchainKHR", + reinterpret_cast(myvkCreateSwapchainKHR)); + vk2.defineSymbol("vkDestroySwapchainKHR", + reinterpret_cast(myvkDestroySwapchainKHR)); Loader::DL::registerFile(vk2); }