feat(bindless): Build descriptor set & compute pipelines for pipelines

This commit is contained in:
PancakeTAS 2026-04-25 20:15:32 +02:00
parent f3d7f1fcea
commit 8ba32ddca6
No known key found for this signature in database
4 changed files with 490 additions and 0 deletions

View file

@ -13,8 +13,10 @@
#include <cstddef>
#include <cstdint>
#include <ios>
#include <memory>
#include <numeric>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
@ -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<vk::Format>((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<UniformBuffer*>(
device.mapMemory(
*this->m_descriptorSet.buffer.second,
0,
VK_WHOLE_SIZE,
{},
dld
)
)};
this->m_descriptorSet.mappedBuffer = std::shared_ptr<UniformBuffer*>(
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<vk::WriteDescriptorSet> writeInfos(4 + signature.descriptors.size());
bindingIdx = 0;
std::array<vk::DescriptorBufferInfo, 1> 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<vk::DescriptorImageInfo, 3> 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<std::vector<vk::DescriptorImageInfo>> 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<uint32_t>(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<vk::ComputePipelineCreateInfo> 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<vk::UniquePipeline> 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")
}

View file

@ -10,7 +10,10 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
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<AllocationInfo, 2> m_allocations;
std::unordered_map<size_t, vk::UniqueDeviceMemory> m_externalAllocations;
/// Vulkan descriptor set
struct DescriptorSet {
vk::UniqueDescriptorPool pool;
vk::DescriptorSet set; // Can not be freed
std::pair<vk::UniqueBuffer, vk::UniqueDeviceMemory> buffer;
std::shared_ptr<UniformBuffer*> mappedBuffer;
std::array<vk::UniqueSampler, 3> samplers;
};
DescriptorSet m_descriptorSet;
vk::UniquePipelineCache m_cache;
std::unordered_map<std::string_view, vk::UniquePipeline> m_pipelines;
};
}

View file

@ -2,11 +2,14 @@
#include "vkhelper.hpp"
#include <algorithm>
#include <array>
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <ios>
#include <iostream>
@ -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<uint8_t> 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<std::streamsize>(file.tellg())};
cacheData = std::vector<uint8_t>(static_cast<size_t>(size));
file.seekg(0, std::ios::beg);
if (!file.read(reinterpret_cast<char*>(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<vk::UniqueDescriptorSetLayout, vk::UniquePipelineLayout> 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<vk::UniqueBuffer, vk::UniqueDeviceMemory> 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<const uint8_t*>(data), // NOLINT (unsafe cast)
size,
reinterpret_cast<uint8_t*>(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<vk::UniqueDescriptorPool, vk::DescriptorSet> 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<vk::DescriptorPoolSize, 4> 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<uint32_t>(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<vk::UniqueImage, vk::UniqueDeviceMemory> vkhelper::createExternalImage(

View file

@ -119,6 +119,21 @@ namespace vkhelper {
const std::span<const uint32_t>& 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<vk::UniqueBuffer, vk::UniqueDeviceMemory> 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<typename T>
std::pair<vk::UniqueBuffer, vk::UniqueDeviceMemory> 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<const void*>(&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<vk::UniqueDescriptorPool, vk::DescriptorSet> 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
///