From 8ba32ddca6e6d738bdf0d20b2bebff89dfbf5ba6 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Sat, 25 Apr 2026 20:15:32 +0200 Subject: [PATCH] feat(bindless): Build descriptor set & compute pipelines for pipelines --- lsfg-vk-backend/src/modules/pipeline.cpp | 180 +++++++++++++++++++++++ lsfg-vk-backend/src/modules/pipeline.hpp | 25 ++++ lsfg-vk-backend/src/utility/vkhelper.cpp | 170 +++++++++++++++++++++ lsfg-vk-backend/src/utility/vkhelper.hpp | 115 +++++++++++++++ 4 files changed, 490 insertions(+) diff --git a/lsfg-vk-backend/src/modules/pipeline.cpp b/lsfg-vk-backend/src/modules/pipeline.cpp index e98ad24..af738b1 100644 --- a/lsfg-vk-backend/src/modules/pipeline.cpp +++ b/lsfg-vk-backend/src/modules/pipeline.cpp @@ -13,8 +13,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -352,5 +354,183 @@ Pipeline::Pipeline( << allocation.segments.size() << " segments") } + // Create image views + for (auto& image : this->m_images) { + const bool hasHdrVariant{image.signature.flags & ImageFlag::HdrVariant}; + const bool isLayered{image.subimages.size() == 1 && image.signature.count > 1}; + + for (auto& subimage : image.subimages) { + subimage.view = vkhelper::createImageView( + dld, + device, + *subimage.image, + static_cast((hasHdrVariant && hdr) + ? image.signature.hdrFormat : image.signature.format), + isLayered ? image.signature.count : 1 + ); + } + } + + // Create the descriptor set & required resources + auto [pool, set] = vkhelper::createDescriptorSet( + dld, + device, + *this->m_layout.layout, + 3, 1, sampledImageCount, storageImageCount + ); + this->m_descriptorSet.pool = std::move(pool); + this->m_descriptorSet.set = set; + + const UniformBuffer buf{ + .advancedColorKind = hdr ? 2U : 0U, + .hdrSupport = hdr ? 1U : 0U, + .resolutionInvScale = 1.0F / flow, + .uiThreshold = 0.5F + }; + this->m_descriptorSet.buffer = vkhelper::createBuffer( + dld, + device, + physdev, + buf + ); + auto* mapped{static_cast( + device.mapMemory( + *this->m_descriptorSet.buffer.second, + 0, + VK_WHOLE_SIZE, + {}, + dld + ) + )}; + this->m_descriptorSet.mappedBuffer = std::shared_ptr( + new UniformBuffer*{mapped}, + [device, memory = *this->m_descriptorSet.buffer.second, dld](auto* ptr) { + device.unmapMemory(memory, dld); + delete ptr; // NOLINT (manual memory management) + } + ); + this->m_descriptorSet.samplers.at(0) = vkhelper::createSampler( + dld, + device, + vk::SamplerAddressMode::eClampToBorder, + vk::CompareOp::eNever, + false + ); + this->m_descriptorSet.samplers.at(1) = vkhelper::createSampler( + dld, + device, + vk::SamplerAddressMode::eClampToBorder, + vk::CompareOp::eNever, + true + ); + this->m_descriptorSet.samplers.at(2) = vkhelper::createSampler( + dld, + device, + vk::SamplerAddressMode::eClampToEdge, + vk::CompareOp::eAlways, + false + ); + + // Update descriptor set bindings + std::vector writeInfos(4 + signature.descriptors.size()); + bindingIdx = 0; + + std::array bufferInfos; + bufferInfos.at(0) = { + .buffer = *this->m_descriptorSet.buffer.first, + .range = VK_WHOLE_SIZE + }; + writeInfos.at(0) = { + .dstSet = this->m_descriptorSet.set, + .dstBinding = bindingIdx++, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = bufferInfos.data() + }; + + std::array samplerInfos; + for (uint32_t i = 0; i < 3; i++) { + auto& writeInfo{writeInfos.at(bindingIdx)}; + + samplerInfos.at(i) = { + .sampler = *this->m_descriptorSet.samplers.at(i) + }; + writeInfo = { + .dstSet = this->m_descriptorSet.set, + .dstBinding = bindingIdx++, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eSampler, + .pImageInfo = &samplerInfos.at(i) + }; + } + + std::vector> imageInfos2D(signature.descriptors.size()); + for (const auto& binding : signature.descriptors) { + auto& writeInfo{writeInfos.at(bindingIdx)}; + + auto& imageInfos{imageInfos2D.at(bindingIdx - 4)}; + imageInfos.reserve(binding.resources.size()); + + for (const auto& resourceIdx : binding.resources) { + const auto& image{this->m_images.at(resourceIdx)}; + + for (const auto& subimage : image.subimages) { + imageInfos.push_back({ + .imageView = *subimage.view, + .imageLayout = vk::ImageLayout::eGeneral + }); + } + } + + writeInfo = { + .dstSet = this->m_descriptorSet.set, + .dstBinding = bindingIdx++, + .descriptorCount = static_cast(imageInfos.size()), + .descriptorType = binding.type == BindingType::StorageImage ? + vk::DescriptorType::eStorageImage : vk::DescriptorType::eSampledImage, + .pImageInfo = imageInfos.data() + }; + } + + device.updateDescriptorSets(writeInfos, {}, dld); + + LOG_DEBUG(" Updated descriptor set with " << writeInfos.size() << " bindings") + + // Build all shader pipelines + std::vector pipelineCreateInfos; + for (const auto& [name, variant] : signature.shaders) { + std::string name2{name}; + if (variant) name2 += hdr ? "_16bit" : "_8bit"; + + const auto& module{library.shader(name2, perf)}; + + pipelineCreateInfos.push_back({ + .stage = { + .stage = vk::ShaderStageFlagBits::eCompute, + .module = *module, + .pName = "main" + }, + .layout = *this->m_layout.pipelineLayout + }); + } + + this->m_cache = vkhelper::createPipelineCache(dld, device); + std::vector pipelines{ + device.createComputePipelinesUnique( + *this->m_cache, + pipelineCreateInfos, + nullptr, + dld + ).value + }; + + this->m_pipelines.reserve(signature.shaders.size()); + for (size_t i = 0; i < signature.shaders.size(); i++) { + const auto& name{signature.shaders.at(i).first}; + this->m_pipelines.emplace(name, std::move(pipelines.at(i))); + } + + LOG_DEBUG(" Created " << this->m_pipelines.size() << " pipelines") + LOG_DEBUG("Finished building pipeline") } diff --git a/lsfg-vk-backend/src/modules/pipeline.hpp b/lsfg-vk-backend/src/modules/pipeline.hpp index 2d11a0f..1df221e 100644 --- a/lsfg-vk-backend/src/modules/pipeline.hpp +++ b/lsfg-vk-backend/src/modules/pipeline.hpp @@ -10,7 +10,10 @@ #include #include #include +#include +#include #include +#include #include namespace lsfgvk::pipeline { @@ -97,6 +100,15 @@ namespace lsfgvk::pipeline { return this->m_externalOutputs; } + /// + /// Get the mapped uniform buffer + /// + /// @return Mapped uniform buffer + /// + [[nodiscard]] auto* getMappedBuffer() const { + return *this->m_descriptorSet.mappedBuffer.get(); + } + private: /// Vulkan descriptor set & pipeline layout struct Layout { @@ -145,6 +157,19 @@ namespace lsfgvk::pipeline { }; std::array m_allocations; std::unordered_map m_externalAllocations; + + /// Vulkan descriptor set + struct DescriptorSet { + vk::UniqueDescriptorPool pool; + vk::DescriptorSet set; // Can not be freed + std::pair buffer; + std::shared_ptr mappedBuffer; + std::array samplers; + }; + DescriptorSet m_descriptorSet; + + vk::UniquePipelineCache m_cache; + std::unordered_map m_pipelines; }; } diff --git a/lsfg-vk-backend/src/utility/vkhelper.cpp b/lsfg-vk-backend/src/utility/vkhelper.cpp index ecd6016..255573b 100644 --- a/lsfg-vk-backend/src/utility/vkhelper.cpp +++ b/lsfg-vk-backend/src/utility/vkhelper.cpp @@ -2,11 +2,14 @@ #include "vkhelper.hpp" +#include #include #include #include #include #include +#include +#include #include #include #include @@ -176,6 +179,45 @@ vk::UniqueShaderModule vkhelper::createShaderModule( return device.createShaderModuleUnique(shaderInfo, nullptr, dld); } +vk::UniquePipelineCache vkhelper::createPipelineCache( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device +) { + // Find the cache file path + std::filesystem::path path{"/tmp/lsfg-vk_pipeline_cache.bin"}; + + const char* xdgCacheHome{std::getenv("XDG_CACHE_HOME")}; + if (xdgCacheHome && *xdgCacheHome != '\0') + path = std::filesystem::path(xdgCacheHome) / "lsfg-vk_pipeline_cache.bin"; + + const char* home{std::getenv("HOME")}; + if (home && *home != '\0') + path = std::filesystem::path(home) / ".cache" / "lsfg-vk_pipeline_cache.bin"; + + // Read cache data (if any) + std::vector cacheData{}; + + if (std::filesystem::exists(path)) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file.is_open()) + throw std::runtime_error("Unable to open pipeline cache file for reading"); + + const std::streamsize size{static_cast(file.tellg())}; + cacheData = std::vector(static_cast(size)); + + file.seekg(0, std::ios::beg); + if (!file.read(reinterpret_cast(cacheData.data()), size)) // NOLINT (unsafe cast) + throw std::runtime_error("Unable to read pipeline cache file"); + } + + // Build pipeline cache + const vk::PipelineCacheCreateInfo pipelineCacheInfo{ + .initialDataSize = cacheData.size(), + .pInitialData = cacheData.data() + }; + return device.createPipelineCacheUnique(pipelineCacheInfo, nullptr, dld); +} + std::pair vkhelper::createLayout( const vk::detail::DispatchLoaderDynamic& dld, const vk::Device& device, @@ -230,6 +272,76 @@ vk::UniqueImage vkhelper::createImage( return device.createImageUnique(imageInfo, nullptr, dld); } +vk::UniqueSampler vkhelper::createSampler( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + vk::SamplerAddressMode mode, + vk::CompareOp compare, + bool white +) { + const vk::SamplerCreateInfo samplerInfo{ + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = mode, + .addressModeV = mode, + .addressModeW = mode, + .compareOp = compare, + .maxLod = vk::LodClampNone, + .borderColor = white ? + vk::BorderColor::eFloatOpaqueWhite : vk::BorderColor::eFloatTransparentBlack + }; + return device.createSamplerUnique(samplerInfo, nullptr, dld); +} + +std::pair vkhelper::createBuffer( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::PhysicalDevice& physdev, + vk::BufferUsageFlags usage, + const void* data, + size_t size +) { + // Create buffer + const vk::BufferCreateInfo bufferInfo{ + .size = size, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive + }; + auto buffer{device.createBufferUnique(bufferInfo, nullptr, dld)}; + + // Allocate memory + const auto requirements{device.getBufferMemoryRequirements(*buffer, dld)}; + + auto memory{vkhelper::allocateMemory( + dld, + device, + physdev, + requirements.size, + requirements.memoryTypeBits, + true + )}; + + // Bind memory + device.bindBufferMemory(*buffer, *memory, 0, dld); + + // Copy data + if (data) { + void* mapped{device.mapMemory(*memory, 0, size, {}, dld)}; + std::copy_n( + reinterpret_cast(data), // NOLINT (unsafe cast) + size, + reinterpret_cast(mapped) // NOLINT (unsafe cast) + ); + device.unmapMemory(*memory, dld); + } + + return { + std::move(buffer), + std::move(memory) + }; +} + /* Memory allocations */ vk::UniqueDeviceMemory vkhelper::allocateMemory( @@ -275,6 +387,64 @@ vk::UniqueDeviceMemory vkhelper::allocateMemory( return device.allocateMemoryUnique(allocInfo, nullptr, dld); } +/* Descriptors */ + +std::pair vkhelper::createDescriptorSet( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::DescriptorSetLayout& layout, + uint32_t samplers, uint32_t buffers, + uint32_t sampledImages, uint32_t storageImages +) { + const std::array poolSizes{{ + { .type = vk::DescriptorType::eSampler, + .descriptorCount = samplers }, + { .type = vk::DescriptorType::eSampledImage, + .descriptorCount = sampledImages }, + { .type = vk::DescriptorType::eStorageImage, + .descriptorCount = storageImages }, + { .type = vk::DescriptorType::eUniformBuffer, + .descriptorCount = buffers } + }}; + auto pool{device.createDescriptorPoolUnique({ + .flags = vk::DescriptorPoolCreateFlagBits::eUpdateAfterBind, + .maxSets = 1, + .poolSizeCount = static_cast(poolSizes.size()), + .pPoolSizes = poolSizes.data() + }, nullptr, dld)}; + + auto set{device.allocateDescriptorSets({ + .descriptorPool = *pool, + .descriptorSetCount = 1, + .pSetLayouts = &layout + }, dld).at(0)}; + + return{ + std::move(pool), + set + }; +} + +vk::UniqueImageView vkhelper::createImageView( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::Image& image, + vk::Format format, + uint32_t layers +) { + const vk::ImageViewCreateInfo viewInfo{ + .image = image, + .viewType = layers == 1 ? vk::ImageViewType::e2D : vk::ImageViewType::e2DArray, + .format = format, + .subresourceRange = { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = layers + } + }; + return device.createImageViewUnique(viewInfo, nullptr, dld); +} + /* External memory */ std::pair vkhelper::createExternalImage( diff --git a/lsfg-vk-backend/src/utility/vkhelper.hpp b/lsfg-vk-backend/src/utility/vkhelper.hpp index 60fcbc8..58b2030 100644 --- a/lsfg-vk-backend/src/utility/vkhelper.hpp +++ b/lsfg-vk-backend/src/utility/vkhelper.hpp @@ -119,6 +119,21 @@ namespace vkhelper { const std::span& code ); + /// + /// Create and maintain the Vulkan pipeline cache for lsfg-vk + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @return RAII-wrapped Vulkan pipeline cache + /// @throws std::runtime_error on failure + /// + vk::UniquePipelineCache createPipelineCache( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device + ); + + // TODO: Persist pipeline cache + /// /// Create a Vulkan descriptor set layout /// @@ -159,6 +174,62 @@ namespace vkhelper { vk::ImageUsageFlags usage ); + /// + /// Create a Vulkan sampler for lsfg-vk + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param mode Address mode + /// @param compare Comparison mode + /// @param white Black/White border color + /// @return RAII-wrapped Vulkan sampler + /// @throws std::runtime_error on failure + /// + vk::UniqueSampler createSampler( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + vk::SamplerAddressMode mode, + vk::CompareOp compare, + bool white + ); + + // (forward decl) + std::pair createBuffer( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::PhysicalDevice& physdev, + vk::BufferUsageFlags usage, + const void* data, + size_t size + ); + + /// + /// Create a Vulkan buffer for lsfg-vk + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param physdev Physical device + /// @param data Buffer contained data + /// @return RAII-wrapped Vulkan uniform buffer & device memory + /// @throws std::runtime_error on failure + /// + template + std::pair createBuffer( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::PhysicalDevice& physdev, + const T& data + ) { + return createBuffer( + dld, + device, + physdev, + vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eTransferDst, + static_cast(&data), + sizeof(T) + ); + } + /* Memory allocations */ /// @@ -193,6 +264,50 @@ namespace vkhelper { return (size + align - 1) & ~(align - 1); } + /* Descriptors */ + + /// + /// Create a Vulkan descriptor set for lsfg-vk + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param layout Descriptor set layout + /// @param samplers Amount of samplers + /// @param buffers Amount of buffers + /// @param sampledImages Amount of sampled images + /// @param storageImages Amount of storage images + /// @return Vulkan descriptor pool & set + /// @throws std::runtime_error on failure + /// + std::pair createDescriptorSet( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::DescriptorSetLayout& layout, + uint32_t samplers, uint32_t buffers, + uint32_t sampledImages, uint32_t storageImages + ); + + /// + /// Create an image view + /// + /// @param dld Dynamic dispatch loader + /// @param device Vulkan device + /// @param image Vulkan image + /// @param format Image format + /// @param layers Amount of layers in image + /// @return RAII-wrapped Vulkan image view + /// @throws std::runtime_error on failure + /// + vk::UniqueImageView createImageView( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Device& device, + const vk::Image& image, + vk::Format format, + uint32_t layers + ); + + /* External memory */ + /// /// Create a Vulkan image with a fd-exportable dedicated allocation ///