diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a8a0f8..0fb353c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(lsfg-vk-gen) file(GLOB SOURCES "src/loader/*.cpp" + "src/mini/*.cpp" "src/*.cpp" ) diff --git a/include/application.hpp b/include/application.hpp index 7dfc341..045982a 100644 --- a/include/application.hpp +++ b/include/application.hpp @@ -1,6 +1,7 @@ #ifndef APPLICATION_HPP #define APPLICATION_HPP +#include "mini/image.hpp" #include #include @@ -76,7 +77,7 @@ public: Application& operator=(Application&&) = delete; /// Destructor, cleans up resources. - ~Application() = default; // no resources to clean up as of right now. + ~Application(); private: // (non-owned resources) VkDevice device; @@ -93,7 +94,6 @@ private: /// class SwapchainContext { public: - /// /// Create the swapchain context. /// @@ -130,20 +130,24 @@ public: /// Get the swapchain images. [[nodiscard]] const std::vector& getImages() const { return this->images; } - // Non-copyable, trivially moveable and destructible + // Non-copyable, trivially moveable SwapchainContext(const SwapchainContext&) = delete; SwapchainContext& operator=(const SwapchainContext&) = delete; SwapchainContext(SwapchainContext&&) = default; SwapchainContext& operator=(SwapchainContext&&) = default; - ~SwapchainContext() = default; + + /// Destructor, cleans up resources. + ~SwapchainContext(); private: // (non-owned resources) - VkSwapchainKHR swapchain{}; - VkFormat format{}; - VkExtent2D extent{}; + VkSwapchainKHR swapchain; + VkFormat format; + VkExtent2D extent; std::vector images; // (owned resources) + Mini::Image frame_0, frame_1; + int32_t lsfgId; }; #endif // APPLICATION_HPP diff --git a/include/mini/image.hpp b/include/mini/image.hpp new file mode 100644 index 0000000..ed4d182 --- /dev/null +++ b/include/mini/image.hpp @@ -0,0 +1,73 @@ +#ifndef IMAGE_HPP +#define IMAGE_HPP + +#include + +#include + +namespace Mini { + + /// + /// C++ wrapper class for a Vulkan image. + /// + /// This class manages the lifetime of a Vulkan image. + /// + class Image { + public: + Image() noexcept = default; + + /// + /// Create the image and export the backing fd + /// + /// @param device Vulkan device + /// @param physicalDevice Vulkan physical device + /// @param extent Extent of the image in pixels. + /// @param format Vulkan format of the image + /// @param usage Usage flags for the image + /// @param aspectFlags Aspect flags for the image view + /// @param fd Pointer to an integer where the file descriptor will be stored. + /// + /// @throws LSFG::vulkan_error if object creation fails. + /// + Image(VkDevice device, VkPhysicalDevice physicalDevice, VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->image; } + /// Get the Vulkan device memory handle. + [[nodiscard]] auto getMemory() const { return *this->memory; } + /// Get the Vulkan image view handle. + [[nodiscard]] auto getView() const { return *this->view; } + /// Get the extent of the image. + [[nodiscard]] VkExtent2D getExtent() const { return this->extent; } + /// Get the format of the image. + [[nodiscard]] VkFormat getFormat() const { return this->format; } + /// Get the aspect flags of the image. + [[nodiscard]] VkImageAspectFlags getAspectFlags() const { return this->aspectFlags; } + + /// Set the layout of the image. + void setLayout(VkImageLayout layout) { *this->layout = layout; } + /// Get the current layout of the image. + [[nodiscard]] VkImageLayout getLayout() const { return *this->layout; } + + /// Trivially copyable, moveable and destructible + Image(const Image&) noexcept = default; + Image& operator=(const Image&) noexcept = default; + Image(Image&&) noexcept = default; + Image& operator=(Image&&) noexcept = default; + ~Image() = default; + private: + std::shared_ptr image; + std::shared_ptr memory; + std::shared_ptr view; + + std::shared_ptr layout; + + VkExtent2D extent{}; + VkFormat format{}; + VkImageAspectFlags aspectFlags{}; + }; + +} + +#endif // IMAGE_HPP diff --git a/lsfg-vk-gen/src/lsfg.cpp b/lsfg-vk-gen/src/lsfg.cpp index 25c1d38..cf70a7e 100644 --- a/lsfg-vk-gen/src/lsfg.cpp +++ b/lsfg-vk-gen/src/lsfg.cpp @@ -2,6 +2,7 @@ #include "core/device.hpp" #include "core/instance.hpp" #include "context.hpp" +#include "utils.hpp" #include #include @@ -22,6 +23,8 @@ void LSFG::initialize() { instance.emplace(); device.emplace(*instance); + Globals::initializeGlobals(*device); + std::srand(static_cast(std::time(nullptr))); } @@ -61,6 +64,8 @@ void LSFG::finalize() { if (!instance.has_value() && !device.has_value()) return; + Globals::uninitializeGlobals(); + instance.reset(); device.reset(); } diff --git a/src/application.cpp b/src/application.cpp index f6216eb..11dfd84 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,13 +1,16 @@ #include "application.hpp" +#include "log.hpp" +#include "mini/image.hpp" #include - -#include +#include Application::Application(VkDevice device, VkPhysicalDevice physicalDevice, VkQueue graphicsQueue, VkQueue presentQueue) : device(device), physicalDevice(physicalDevice), - graphicsQueue(graphicsQueue), presentQueue(presentQueue) {} + graphicsQueue(graphicsQueue), presentQueue(presentQueue) { + LSFG::initialize(); +} void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2D extent, const std::vector& images) { @@ -20,7 +23,23 @@ void Application::addSwapchain(VkSwapchainKHR handle, VkFormat format, VkExtent2 SwapchainContext::SwapchainContext(const Application& app, VkSwapchainKHR swapchain, VkFormat format, VkExtent2D extent, const std::vector& images) - : swapchain(swapchain), format(format), extent(extent), images(images) {} + : swapchain(swapchain), format(format), extent(extent), images(images) { + int frame0fd{}; + this->frame_0 = Mini::Image( + app.getDevice(), app.getPhysicalDevice(), + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, &frame0fd + ); + int frame1fd{}; + this->frame_1 = Mini::Image( + app.getDevice(), app.getPhysicalDevice(), + extent, VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, &frame1fd + ); + this->lsfgId = LSFG::createContext(extent.width, extent.height, frame0fd, frame1fd); +} void Application::presentSwapchain(VkSwapchainKHR handle, VkQueue queue, const std::vector& semaphores, uint32_t idx) { @@ -46,6 +65,14 @@ void SwapchainContext::present(const Application& app, VkQueue queue, throw LSFG::vulkan_error(res, "Failed to present swapchain"); } +SwapchainContext::~SwapchainContext() { + try { + LSFG::deleteContext(this->lsfgId); + } catch (const std::exception&) { + return; + } +} + bool Application::removeSwapchain(VkSwapchainKHR handle) { auto it = this->swapchains.find(handle); if (it == this->swapchains.end()) @@ -53,3 +80,8 @@ bool Application::removeSwapchain(VkSwapchainKHR handle) { this->swapchains.erase(it); return true; } + +Application::~Application() { + this->swapchains.clear(); + LSFG::finalize(); +} diff --git a/src/mini/image.cpp b/src/mini/image.cpp new file mode 100644 index 0000000..831c869 --- /dev/null +++ b/src/mini/image.cpp @@ -0,0 +1,142 @@ +#include "mini/image.hpp" +#include "lsfg.hpp" + +#include +#include + +using namespace Mini; + +Image::Image(VkDevice device, VkPhysicalDevice physicalDevice, + VkExtent2D extent, VkFormat format, + VkImageUsageFlags usage, VkImageAspectFlags aspectFlags, int* fd) + : extent(extent), format(format), aspectFlags(aspectFlags) { + // create image + const VkExternalMemoryImageCreateInfo externalInfo{ + .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR + }; + const VkImageCreateInfo desc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = &externalInfo, + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE + }; + VkImage imageHandle{}; + auto res = vkCreateImage(device, &desc, nullptr, &imageHandle); + if (res != VK_SUCCESS || imageHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create Vulkan image"); + + // find memory type + VkPhysicalDeviceMemoryProperties memProps; + vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps); + + VkMemoryRequirements memReqs; + vkGetImageMemoryRequirements(device, imageHandle, &memReqs); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + std::optional memType{}; + for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && // NOLINTBEGIN + (memProps.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { + memType.emplace(i); + break; + } // NOLINTEND + } + if (!memType.has_value()) + throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unable to find memory type for image"); +#pragma clang diagnostic pop + + // allocate and bind memory + const VkMemoryDedicatedAllocateInfoKHR dedicatedInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR, + .image = imageHandle, + }; + const VkExportMemoryAllocateInfo exportInfo{ + .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, + .pNext = &dedicatedInfo, + .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR + }; + const VkMemoryAllocateInfo allocInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = &exportInfo, + .allocationSize = memReqs.size, + .memoryTypeIndex = memType.value() + }; + VkDeviceMemory memoryHandle{}; + res = vkAllocateMemory(device, &allocInfo, nullptr, &memoryHandle); + if (res != VK_SUCCESS || memoryHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to allocate memory for Vulkan image"); + + res = vkBindImageMemory(device, imageHandle, memoryHandle, 0); + if (res != VK_SUCCESS) + throw LSFG::vulkan_error(res, "Failed to bind memory to Vulkan image"); + + // create image view + const VkImageViewCreateInfo viewDesc{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = imageHandle, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY + }, + .subresourceRange = { + .aspectMask = aspectFlags, + .levelCount = 1, + .layerCount = 1 + } + }; + + VkImageView viewHandle{}; + res = vkCreateImageView(device, &viewDesc, nullptr, &viewHandle); + if (res != VK_SUCCESS || viewHandle == VK_NULL_HANDLE) + throw LSFG::vulkan_error(res, "Failed to create image view"); + + // obtain the sharing fd + auto vkGetMemoryFdKHR = + reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetMemoryFdKHR")); + + const VkMemoryGetFdInfoKHR fdInfo{ + .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, + .memory = memoryHandle, + .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR, + }; + res = vkGetMemoryFdKHR(device, &fdInfo, fd); + if (res != VK_SUCCESS || *fd < 0) + throw LSFG::vulkan_error(res, "Failed to obtain sharing fd for Vulkan image"); + + // store objects in shared ptr + this->layout = std::make_shared(VK_IMAGE_LAYOUT_UNDEFINED); + this->image = std::shared_ptr( + new VkImage(imageHandle), + [dev = device](VkImage* img) { + vkDestroyImage(dev, *img, nullptr); + } + ); + this->memory = std::shared_ptr( + new VkDeviceMemory(memoryHandle), + [dev = device](VkDeviceMemory* mem) { + vkFreeMemory(dev, *mem, nullptr); + } + ); + this->view = std::shared_ptr( + new VkImageView(viewHandle), + [dev = device](VkImageView* imgView) { + vkDestroyImageView(dev, *imgView, nullptr); + } + ); +}