feat(bindless): Build images for pipelines

This commit is contained in:
PancakeTAS 2026-04-25 20:08:07 +02:00
parent 8e7dd4e4aa
commit 310f53e373
No known key found for this signature in database
4 changed files with 333 additions and 0 deletions

View file

@ -8,7 +8,13 @@
#include "utility/logger.hpp"
#include "utility/vkhelper.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <ios>
#include <stdexcept>
#include <unordered_map>
#include <utility>
#include <vector>
@ -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<uint32_t>(static_cast<float>(extent.width) * flow),
static_cast<uint32_t>(static_cast<float>(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<vk::Format>(image.signature.hdrFormat) :
static_cast<vk::Format>(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")
}

View file

@ -4,14 +4,33 @@
#include "library.hpp"
#include "pipeline/signature.hpp"
#include "pipeline/signature/image.hpp"
#include "utility/vkhelper.hpp"
#include <cstddef>
#include <cstdint>
#include <unordered_map>
#include <vector>
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<SubImage> subimages;
vk::DeviceSize size{};
};
std::vector<Image> m_images;
std::vector<ExternalImage> m_externalInputs;
std::vector<ExternalImage> m_externalOutputs;
std::unordered_map<size_t, vk::UniqueDeviceMemory> m_externalAllocations;
};
}

View file

@ -3,12 +3,14 @@
#include "vkhelper.hpp"
#include <array>
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <ios>
#include <iostream>
#include <optional>
#include <span>
#include <sstream>
#include <stdexcept>
@ -201,3 +203,102 @@ std::pair<vk::UniqueDescriptorSetLayout, vk::UniquePipelineLayout> 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<vk::UniqueImage, vk::UniqueDeviceMemory> 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<uint32_t> 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)
};
}

View file

@ -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<vk::UniqueImage, vk::UniqueDeviceMemory> 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
);
}