From 310f53e373b4db0c877c12a68cfa92cf445cb982 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 25 Apr 2026 20:08:07 +0200 Subject: [PATCH] feat(bindless): Build images for pipelines --- lsfg-vk-backend/src/modules/pipeline.cpp | 120 +++++++++++++++++++++++ lsfg-vk-backend/src/modules/pipeline.hpp | 53 ++++++++++ lsfg-vk-backend/src/utility/vkhelper.cpp | 101 +++++++++++++++++++ lsfg-vk-backend/src/utility/vkhelper.hpp | 59 +++++++++++ 4 files changed, 333 insertions(+) diff --git a/lsfg-vk-backend/src/modules/pipeline.cpp b/lsfg-vk-backend/src/modules/pipeline.cpp index ed3955b..3eeb6ff 100644 --- a/lsfg-vk-backend/src/modules/pipeline.cpp +++ b/lsfg-vk-backend/src/modules/pipeline.cpp @@ -8,7 +8,13 @@ #include "utility/logger.hpp" #include "utility/vkhelper.hpp" +#include +#include +#include #include +#include +#include +#include #include #include @@ -108,5 +114,119 @@ Pipeline::Pipeline( << sampledImageCount << " sampled images, " << storageImageCount << " storage images)") + // Create the Vulkan images + size_t alignment{}; + uint32_t types{~0U}; + + const vk::Extent2D flowExtent{ + static_cast(static_cast(extent.width) * flow), + static_cast(static_cast(extent.height) * flow) + }; + for (const auto& imageSignature : signature.images) { + const auto imageIdx{this->m_images.size()}; + auto& image{this->m_images.emplace_back()}; + image = { + .signature = imageSignature + }; + + const bool hasHdrVariant{image.signature.flags & ImageFlag::HdrVariant}; + const vk::Format format{ + (hasHdrVariant && hdr) ? + static_cast(image.signature.hdrFormat) : + static_cast(image.signature.format) + }; + const vk::Extent2D baseExtent{apply(extent, flowExtent, image.signature.extentOp)}; + const vk::ImageUsageFlags usage{ + vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eSampled + }; + + const bool isMipmapped{image.signature.flags & ImageFlag::Mipmaps}; + for (uint32_t i = 0; i < image.signature.count; i++) { + const vk::Extent2D imageExtent{ + .width = std::max(baseExtent.width >> i, 1U), + .height = std::max(baseExtent.height >> i, 1U) + }; + + if (image.signature.flags & (ImageFlag::ExternalInput | ImageFlag::ExternalOutput)) { + const bool isInputOr{image.signature.flags & ImageFlag::ExternalInput}; + + auto [subimage, allocation] = vkhelper::createExternalImage( + dld, + device, + physdev, + imageExtent, + format, + image.signature.count, + usage | + (isInputOr ? + vk::ImageUsageFlagBits::eTransferDst + : vk::ImageUsageFlagBits::eTransferSrc) + ); + + if (isInputOr) { + this->m_externalInputs.push_back({ + .extent = imageExtent, + .format = format, + .layers = image.signature.count, + .image = *subimage, + .memory = *allocation + }); + } else { + this->m_externalOutputs.push_back({ + .extent = imageExtent, + .format = format, + .layers = image.signature.count, + .image = *subimage, + .memory = *allocation + }); + } + + LOG_DEBUG(" Allocated memory of size " + << [&]() { + const auto& reqs{device.getImageMemoryRequirements(*subimage, dld)}; + return reqs.size; + }() << " for external image " << imageIdx) + + image.subimages.push_back({ + .image = std::move(subimage) + }); + this->m_externalAllocations[imageIdx] = std::move(allocation); + + break; // There can only be one image + } + + image.subimages.push_back({ + .image = vkhelper::createImage( + dld, + device, + imageExtent, + format, + isMipmapped ? 1 : image.signature.count, + usage + ) + }); + + if (!isMipmapped) { + break; + } + } + + for (auto& subimage : image.subimages) { + subimage.memory = device.getImageMemoryRequirements(*subimage.image, dld); + + if (image.signature.flags & (ImageFlag::ExternalInput | ImageFlag::ExternalOutput)) + break; + + alignment = std::max(alignment, subimage.memory.alignment); + types &= subimage.memory.memoryTypeBits; + } + } + + if (types == 0) + throw std::runtime_error("No compatible memory type found for pipeline images"); + + LOG_DEBUG(" Created " << this->m_images.size() << " images with common alignment " + << alignment << " and memory type bits " << std::hex << types << std::dec) + LOG_DEBUG("Finished building pipeline") } diff --git a/lsfg-vk-backend/src/modules/pipeline.hpp b/lsfg-vk-backend/src/modules/pipeline.hpp index 0894e8b..32484f6 100644 --- a/lsfg-vk-backend/src/modules/pipeline.hpp +++ b/lsfg-vk-backend/src/modules/pipeline.hpp @@ -4,14 +4,33 @@ #include "library.hpp" #include "pipeline/signature.hpp" +#include "pipeline/signature/image.hpp" #include "utility/vkhelper.hpp" +#include #include +#include +#include namespace lsfgvk::pipeline { // TODO: Improve API design + /// Handle to an external image + struct ExternalImage { + /// Image Extent + vk::Extent2D extent; + /// Image Format + vk::Format format; + /// Amount of layers in image + uint32_t layers; + + /// Handle to the Vulkan image (not owned) + vk::Image image; + /// Handle to the Vulkan memory (not owned) + vk::DeviceMemory memory; + }; + /// Struct for the uniform buffer struct UniformBuffer { float timestamp; @@ -63,6 +82,20 @@ namespace lsfgvk::pipeline { bool hdr ); + /// + /// Get all external input images + /// + /// @return List of images + /// + [[nodiscard]] auto& getExternalInputs() const { + return this->m_externalInputs; + } + + /// Get all external output images + [[nodiscard]] auto& getExternalOutputs() const { + return this->m_externalOutputs; + } + private: /// Vulkan descriptor set & pipeline layout struct Layout { @@ -70,6 +103,26 @@ namespace lsfgvk::pipeline { vk::UniquePipelineLayout pipelineLayout; }; Layout m_layout; + + /// Sub-image of a Vulkan image + struct SubImage { + vk::UniqueImage image; + vk::MemoryRequirements memory; + vk::UniqueImageView view; + }; + + /// Vulkan image created from an ImageSignature + struct Image { + ImageSignature signature; + std::vector subimages; + vk::DeviceSize size{}; + }; + std::vector m_images; + + std::vector m_externalInputs; + std::vector m_externalOutputs; + + std::unordered_map m_externalAllocations; }; } diff --git a/lsfg-vk-backend/src/utility/vkhelper.cpp b/lsfg-vk-backend/src/utility/vkhelper.cpp index 19ba529..cdb08c2 100644 --- a/lsfg-vk-backend/src/utility/vkhelper.cpp +++ b/lsfg-vk-backend/src/utility/vkhelper.cpp @@ -3,12 +3,14 @@ #include "vkhelper.hpp" #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -201,3 +203,102 @@ std::pair vkhelper::cre return { std::move(descriptorSetLayout), std::move(pipelineLayout) }; } + +/* Resources */ + +vk::UniqueImage vkhelper::createImage( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + vk::Extent2D extent, + vk::Format format, + uint32_t layers, + vk::ImageUsageFlags usage +) { + const vk::ImageCreateInfo imageInfo{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = layers, + .samples = vk::SampleCountFlagBits::e1, + .usage = usage + }; + return device.createImageUnique(imageInfo, nullptr, dld); +} + +/* External memory */ + +std::pair vkhelper::createExternalImage( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::PhysicalDevice& physdev, + vk::Extent2D extent, + vk::Format format, + uint32_t layers, + vk::ImageUsageFlags usage +) { + const vk::ExternalMemoryImageCreateInfo externalInfo{ + .handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eOpaqueFd + }; + const vk::ImageCreateInfo imageInfo{ + .pNext = &externalInfo, + .imageType = vk::ImageType::e2D, + .format = format, + .extent = { + .width = extent.width, + .height = extent.height, + .depth = 1 + }, + .mipLevels = 1, + .arrayLayers = layers, + .samples = vk::SampleCountFlagBits::e1, + .usage = usage + }; + auto image{device.createImageUnique(imageInfo, nullptr, dld)}; + + // Find a suitable memory type index + const auto memProps{physdev.getMemoryProperties2(dld)}; + const auto requirements{device.getImageMemoryRequirements(*image, dld)}; + + std::optional selectedTypeIdx{}; + for (uint32_t i = 0; i < memProps.memoryProperties.memoryTypeCount; i++) { + if (!std::bitset<32>(requirements.memoryTypeBits).test(i)) + continue; + const auto& memType{memProps.memoryProperties.memoryTypes.at(i)}; + + if (memType.propertyFlags & vk::MemoryPropertyFlagBits::eDeviceLocal) { + selectedTypeIdx = i; + break; + } + } + + if (!selectedTypeIdx) + throw std::runtime_error("No suitable memory type found for allocation"); + + // Allocate memory + const vk::MemoryDedicatedAllocateInfo dedicatedInfo{ + .image = *image, + }; + const vk::ExportMemoryAllocateInfo exportInfo{ + .pNext = &dedicatedInfo, + .handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eOpaqueFd + }; + const vk::MemoryAllocateInfo allocInfo{ + .pNext = &exportInfo, + .allocationSize = requirements.size, + .memoryTypeIndex = *selectedTypeIdx + }; + auto memory{device.allocateMemoryUnique(allocInfo, nullptr, dld)}; + + // Bind memory + device.bindImageMemory(*image, *memory, 0, dld); + + return{ + std::move(image), + std::move(memory) + }; +} diff --git a/lsfg-vk-backend/src/utility/vkhelper.hpp b/lsfg-vk-backend/src/utility/vkhelper.hpp index 0748560..92a3299 100644 --- a/lsfg-vk-backend/src/utility/vkhelper.hpp +++ b/lsfg-vk-backend/src/utility/vkhelper.hpp @@ -135,4 +135,63 @@ namespace vkhelper { size_t pushConstantSize ); + /* Resources */ + + /// + /// Create a (unallocated) Vulkan image for lsfg-vk + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param extent Image extent + /// @param format Image format + /// @param layers Amount of images + /// @param usage Image usage flags + /// @return RAII-wrapped Vulkan image + /// @throws std::runtime_error on failure + /// + vk::UniqueImage createImage( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + vk::Extent2D extent, + vk::Format format, + uint32_t layers, + vk::ImageUsageFlags usage + ); + + /// + /// Align a memory allocation + /// + /// @param size Memory size + /// @param align Alignment + /// @return Aligned memory size + /// + inline vk::DeviceSize align(vk::DeviceSize size, vk::DeviceSize align) noexcept { + return (size + align - 1) & ~(align - 1); + } + + /* External memory */ + + /// + /// Create a Vulkan image with a fd-exportable dedicated allocation + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param physdev Physical device + /// @param extent Image extent + /// @param format Image format + /// @param layers Amount of images + /// @param usage Image usage flags + /// @return RAII-wrapped Vulkan image + /// @throws std::runtime_error on failure + /// + std::pair createExternalImage( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::PhysicalDevice& physdev, + vk::Extent2D extent, + vk::Format format, + uint32_t layers, + vk::ImageUsageFlags usage + ); + }