refactor: add multiset helper

This commit is contained in:
PancakeTAS 2025-09-01 21:32:20 +02:00
parent 29ee02f080
commit 32d35c25fc
No known key found for this signature in database
8 changed files with 322 additions and 112 deletions

View file

@ -94,8 +94,8 @@ namespace VK::Core {
/// @throws std::logic_error if the command buffer is not in Recording state
///
void insertBarrier(
const std::vector<VkImage>& readableImages,
const std::vector<VkImage>& writableImages) const;
const std::vector<std::optional<Core::Image>>& readableImages,
const std::vector<Core::Image>& writableImages) const;
///
/// Copy a buffer to an image.

View file

@ -14,52 +14,6 @@
namespace VK::Core {
/// Helper class to wrap VkDescriptorImageInfo
class ImageInfo {
public:
ImageInfo() noexcept = default; // skipping images is allowed
ImageInfo(const Image& image) noexcept
: info{
.imageView = image.getView(),
.imageLayout = VK_IMAGE_LAYOUT_GENERAL
} {}
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return &this->info; }
private:
VkDescriptorImageInfo info{};
};
/// Helper class to wrap VkDescriptorImageInfo for samplers
class SamplerInfo {
public:
SamplerInfo(const Sampler& sampler) noexcept
: info{
.sampler = sampler.handle(),
} {}
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return &this->info; }
private:
VkDescriptorImageInfo info{};
};
/// Helper class to wrap VkDescriptorBufferInfo
class BufferInfo {
public:
BufferInfo(const Buffer& buffer) noexcept
: info{
.buffer = buffer.handle(),
.range = buffer.getSize()
} {}
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return &this->info; }
private:
VkDescriptorBufferInfo info{};
};
///
/// C++ wrapper class for a Vulkan descriptor set.
///
@ -74,16 +28,20 @@ namespace VK::Core {
///
/// @param device Vulkan device
/// @param pool Descriptor pool to allocate from
/// @param shaderModule Shader module to use for the descriptor set
/// @param shaderModule Shader module this descriptor is for
/// @param sampledImages Sampled images to bind
/// @param storageImages Storage images to bind
/// @param samplers Samplers to bind
/// @param buffer Buffer to bind
///
/// @throws VK::vulkan_error if object creation fails.
///
DescriptorSet(const Device& device,
const DescriptorPool& pool, const ShaderModule& shaderModule,
const std::vector<ImageInfo>& sampledImages,
const std::vector<ImageInfo>& storageImages,
const std::vector<SamplerInfo>& samplers,
const std::vector<BufferInfo>& uniformBuffers);
const std::vector<std::optional<Core::Image>>& sampledImages,
const std::vector<Core::Image>& storageImages,
const std::vector<Core::Sampler>& samplers,
const std::optional<Core::Buffer>& buffer);
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->descriptorSet; }

View file

@ -6,7 +6,6 @@
#include <cstdint>
#include <cstddef>
#include <utility>
#include <vector>
#include <memory>
@ -26,12 +25,18 @@ namespace VK::Core {
///
/// @param device Vulkan device
/// @param code SPIR-V bytecode for the shader.
/// @param descriptorTypes Descriptor types used in the shader.
/// @param sampledImages Number of sampled images in the shader.
/// @param storageImages Number of storage images in the shader.
/// @param buffers Number of uniform/storage buffers in the shader.
/// @param samplers Number of samplers in the shader.
///
/// @throws VK::vulkan_error if object creation fails.
///
ShaderModule(const Device& device, const std::vector<uint8_t>& code,
const std::vector<std::pair<size_t, VkDescriptorType>>& descriptorTypes);
size_t sampledImages,
size_t storageImages,
size_t buffers,
size_t samplers);
/// Get the Vulkan handle.
[[nodiscard]] auto handle() const { return *this->shaderModule; }

View file

@ -0,0 +1,195 @@
#pragma once
#include "vk/helper/mipmapped_image.hpp"
#include "vk/helper/temporal_image.hpp"
#include "vk/helper/image_group.hpp"
#include "vk/core/descriptorpool.hpp"
#include "vk/core/descriptorset.hpp"
#include "vk/core/shadermodule.hpp"
#include "vk/core/sampler.hpp"
#include "vk/core/buffer.hpp"
#include "vk/core/image.hpp"
#include <optional>
#include <cstddef>
#include <vector>
namespace VK::Helper {
///
/// A list of descriptor sets across multiple frames.
///
class MultiSet {
friend class MultiSetBuilder;
public:
MultiSet() noexcept = default;
/// Get the amount of descriptor sets.
[[nodiscard]] auto size() const noexcept { return this->sets.size(); }
/// Get all the descriptor sets.
[[nodiscard]] const auto& all() const { return this->sets; }
/// Get the descriptor set for a specific frame index.
[[nodiscard]] const auto& at(size_t index) const {
return this->set(index).sets;
}
/// Get the readable images for a specific frame index.
[[nodiscard]] const auto& readablesAt(size_t index) const {
return this->set(index).readables;
}
/// Get the writable images for a specific frame index.
[[nodiscard]] const auto& writablesAt(size_t index) const {
return this->set(index).writables;
}
private:
struct Set {
Core::DescriptorSet sets;
std::vector<std::optional<Core::Image>> readables;
std::vector<Core::Image> writables;
};
std::vector<Set> sets;
/// Get the set at a specific index.
[[nodiscard]] const Set& set(size_t index) const {
return this->sets.at(index % this->sets.size());
}
};
///
/// Builder class for a shader descriptors across multiple frames.
///
class MultiSetBuilder {
public:
MultiSetBuilder() noexcept = default;
///
/// Create a new descriptor builder.
///
/// @param count Amount of sets to create.
///
MultiSetBuilder(size_t count) noexcept : recipes(count) {}
/// Add a buffer to the descriptor.
MultiSetBuilder& withBuffer(const Core::Buffer& buffer) {
this->buffer = buffer;
return *this;
}
/// Add a sampler to the descriptor.
MultiSetBuilder& withSampler(const Core::Sampler& sampler) {
this->samplers = { sampler };
return *this;
}
/// Add two samplers to the descriptor.
MultiSetBuilder& withSamplers(
const Core::Sampler& sampler1, const Core::Sampler& sampler2) {
this->samplers = { sampler1, sampler2 };
return *this;
}
/// Add an input image to the descriptor.
MultiSetBuilder& addInput(const Core::Image& image) {
for (auto& recipe : this->recipes)
recipe.inImages.emplace_back(image);
return *this;
}
/// Add an optional input image to the descriptor.
MultiSetBuilder& addInput(const std::optional<Core::Image>& image) {
for (auto& recipe : this->recipes)
recipe.inImages.emplace_back(image);
return *this;
}
/// Add multiple input images to the descriptor.
MultiSetBuilder& addInput(const std::vector<Core::Image>& images) {
for (auto& recipe : this->recipes)
for (const auto& img : images)
recipe.inImages.emplace_back(img);
return *this;
}
/// Add a temporal input image to the descriptor.
MultiSetBuilder& addInput(const TemporalImage& temporalImage, size_t offset = 0) {
for (auto& recipe : this->recipes)
recipe.inImages.emplace_back(temporalImage.at(offset++));
return *this;
}
/// Add a group of input images to the descriptor.
MultiSetBuilder& addInput(const ImageGroup& imageGroup) {
this->addInput(imageGroup.into());
return *this;
}
/// Add a subgroup of input images to the descriptor.
MultiSetBuilder& addInput(const ImageGroup& imageGroup, size_t start, size_t length) {
this->addInput(imageGroup.subGroup(start, length));
return *this;
}
/// Add all mipmapped images to the descriptor.
MultiSetBuilder& addInput(const Helper::MipmappedImage& mipmappedImage) {
this->addInput(mipmappedImage.into());
return *this;
}
/// Add an output image to the descriptor.
MultiSetBuilder& addOutput(const Core::Image& image) {
for (auto& recipe : this->recipes)
recipe.outImages.push_back(image);
return *this;
}
/// Add multiple output images to the descriptor.
MultiSetBuilder& addOutput(const std::vector<Core::Image>& images) {
for (auto& recipe : this->recipes)
for (const auto& img : images)
recipe.outImages.push_back(img);
return *this;
}
/// Add a temporal output image to the descriptor.
MultiSetBuilder& addOutput(const TemporalImage& temporalImage, size_t offset = 0) {
for (auto& recipe : this->recipes)
recipe.outImages.push_back(temporalImage.at(offset++));
return *this;
}
/// Add a group of output images to the descriptor.
MultiSetBuilder& addOutput(const ImageGroup& imageGroup) {
this->addOutput(imageGroup.into());
return *this;
}
/// Add a subgroup of output images to the descriptor.
MultiSetBuilder& addOutput(const ImageGroup& imageGroup, size_t start, size_t length) {
this->addOutput(imageGroup.subGroup(start, length));
return *this;
}
/// Add all mipmapped images to the descriptor.
MultiSetBuilder& addOutput(const Helper::MipmappedImage& mipmappedImage) {
this->addOutput(mipmappedImage.into());
return *this;
}
///
/// Build the descriptor sets.
///
/// @param device The Vulkan device.
/// @param descriptorPool The descriptor pool to allocate from.
/// @param shaderModule The shader module this descriptor is for.
/// @return The built descriptor sets.
///
[[nodiscard]] MultiSet build(
const Core::Device& device,
const Core::DescriptorPool& descriptorPool,
const Core::ShaderModule& shaderModule) const;
private:
std::optional<Core::Buffer> buffer;
std::vector<Core::Sampler> samplers;
struct SetRecipe {
std::vector<std::optional<Core::Image>> inImages;
std::vector<Core::Image> outImages;
};
std::vector<SetRecipe> recipes;
};
}

View file

@ -106,8 +106,8 @@ void CommandBuffer::insertBarrier(
}
void CommandBuffer::insertBarrier(
const std::vector<VkImage>& readableImages,
const std::vector<VkImage>& writableImages) const {
const std::vector<std::optional<Core::Image>>& readableImages,
const std::vector<Core::Image>& writableImages) const {
if (*this->state != CommandBufferState::Recording)
throw std::logic_error("Command buffer is not in Recording state");
@ -123,22 +123,24 @@ void CommandBuffer::insertBarrier(
}
};
const size_t totalImages =
readableImages.size() + writableImages.size();
std::vector<VkImageMemoryBarrier2> barriers(totalImages);
const size_t totalImages = readableImages.size() + writableImages.size();
std::vector<VkImageMemoryBarrier2> barriers;
barriers.reserve(totalImages);
for (const auto& image : readableImages) {
if (!image.has_value())
continue;
VkImageMemoryBarrier2& barrier = barriers.emplace_back(dummyBarrier);
barrier.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT;
barrier.image = image;
barrier.image = image->handle();
}
for (const auto& image : writableImages) {
VkImageMemoryBarrier2& barrier = barriers.emplace_back(dummyBarrier);
barrier.srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT;
barrier.dstAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT;
barrier.image = image;
barrier.image = image.handle();
}
// insert barriers

View file

@ -4,9 +4,13 @@
#include "vk/core/descriptorpool.hpp"
#include "vk/core/descriptorset.hpp"
#include "vk/core/shadermodule.hpp"
#include "vk/core/sampler.hpp"
#include "vk/core/buffer.hpp"
#include "vk/core/device.hpp"
#include "vk/core/image.hpp"
#include "vk/exception.hpp"
#include <optional>
#include <cstddef>
#include <cstdint>
#include <memory>
@ -16,10 +20,10 @@ using namespace VK::Core;
DescriptorSet::DescriptorSet(const Device& device,
const DescriptorPool& pool, const ShaderModule& shaderModule,
const std::vector<ImageInfo>& sampledImages,
const std::vector<ImageInfo>& storageImages,
const std::vector<SamplerInfo>& samplers,
const std::vector<BufferInfo>& uniformBuffers) {
const std::vector<std::optional<Core::Image>>& sampledImages,
const std::vector<Core::Image>& storageImages,
const std::vector<Core::Sampler>& samplers,
const std::optional<Core::Buffer>& buffer) {
// create descriptor set
VkDescriptorSetLayout layout = shaderModule.getLayout();
const VkDescriptorSetAllocateInfo desc{
@ -33,22 +37,30 @@ DescriptorSet::DescriptorSet(const Device& device,
if (res != VK_SUCCESS || descriptorSetHandle == VK_NULL_HANDLE)
throw VK::vulkan_error(res, "Unable to allocate descriptor set");
// create descriptor writes
const size_t totalEntries =
storageImages.size() + samplers.size() + uniformBuffers.size() + sampledImages.size();
std::vector<VkWriteDescriptorSet> entries(totalEntries);
const size_t bindingCount = samplers.size() + sampledImages.size()
+ storageImages.size() + (buffer.has_value() ? 1 : 0);
size_t bufferIdx{0};
for (const auto& buf : uniformBuffers)
// create descriptor writes
std::vector<VkWriteDescriptorSet> entries;
entries.reserve(bindingCount);
std::optional<VkDescriptorBufferInfo> bufferInfos;
if (buffer.has_value())
entries.push_back({
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = descriptorSetHandle,
.dstBinding = static_cast<uint32_t>(bufferIdx++),
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.pBufferInfo = buf.handle()
.pBufferInfo = &(bufferInfos = VkDescriptorBufferInfo{
.buffer = buffer->handle(),
.range = buffer->getSize()
}).value()
});
std::vector<VkDescriptorImageInfo> imageInfos;
imageInfos.reserve(bindingCount);
size_t samplerIdx{16};
for (const auto& samp : samplers)
entries.push_back({
@ -57,19 +69,26 @@ DescriptorSet::DescriptorSet(const Device& device,
.dstBinding = static_cast<uint32_t>(samplerIdx++),
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
.pImageInfo = samp.handle()
.pImageInfo = &(imageInfos.emplace_back(VkDescriptorImageInfo{
.sampler = samp.handle(),
}))
});
size_t inputIdx{32};
for (const auto& img : sampledImages)
for (const auto& img : sampledImages) {
entries.push_back({
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = descriptorSetHandle,
.dstBinding = static_cast<uint32_t>(inputIdx++),
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
.pImageInfo = img.handle()
.pImageInfo = &(imageInfos.emplace_back(VkDescriptorImageInfo{
.imageView = img.has_value() ? img->getView() : nullptr,
.imageLayout = img.has_value() ?
VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED
}))
});
}
size_t outputIdx{48};
for (const auto& img : storageImages)
@ -79,7 +98,10 @@ DescriptorSet::DescriptorSet(const Device& device,
.dstBinding = static_cast<uint32_t>(outputIdx++),
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.pImageInfo = img.handle()
.pImageInfo = &(imageInfos.emplace_back(VkDescriptorImageInfo{
.imageView = img.getView(),
.imageLayout = VK_IMAGE_LAYOUT_GENERAL
}))
});
// update descriptor set

View file

@ -7,14 +7,16 @@
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>
#include <memory>
using namespace VK::Core;
ShaderModule::ShaderModule(const Device& device, const std::vector<uint8_t>& code,
const std::vector<std::pair<size_t, VkDescriptorType>>& descriptorTypes) {
size_t sampledImages,
size_t storageImages,
size_t buffers,
size_t samplers) {
// create shader module
const uint8_t* data_ptr = code.data();
const VkShaderModuleCreateInfo createInfo{
@ -29,39 +31,39 @@ ShaderModule::ShaderModule(const Device& device, const std::vector<uint8_t>& cod
// create descriptor set layout
std::vector<VkDescriptorSetLayoutBinding> layoutBindings;
size_t bufferIdx{0};
size_t samplerIdx{16};
size_t inputIdx{32};
size_t outputIdx{48};
for (const auto &[count, type] : descriptorTypes)
for (size_t i = 0; i < count; i++) {
size_t* bindIdx{};
switch (type) {
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
bindIdx = &bufferIdx;
break;
case VK_DESCRIPTOR_TYPE_SAMPLER:
bindIdx = &samplerIdx;
break;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
bindIdx = &inputIdx;
break;
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
bindIdx = &outputIdx;
break;
default:
throw VK::vulkan_error(VK_ERROR_UNKNOWN, "Unsupported descriptor type");
}
layoutBindings.reserve(buffers + samplers + sampledImages + storageImages);
layoutBindings.emplace_back(VkDescriptorSetLayoutBinding {
.binding = static_cast<uint32_t>(*bindIdx),
.descriptorType = type,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT
});
for (size_t i = 0; i < buffers; i++)
layoutBindings.emplace_back(VkDescriptorSetLayoutBinding {
.binding = static_cast<uint32_t>(i),
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT
});
(*bindIdx)++;
}
for (size_t i = 0; i < samplers; i++)
layoutBindings.emplace_back(VkDescriptorSetLayoutBinding {
.binding = static_cast<uint32_t>(i + 16),
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT
});
for (size_t i = 0; i < sampledImages; i++)
layoutBindings.emplace_back(VkDescriptorSetLayoutBinding {
.binding = static_cast<uint32_t>(i + 32),
.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT
});
for (size_t i = 0; i < storageImages; i++)
layoutBindings.emplace_back(VkDescriptorSetLayoutBinding {
.binding = static_cast<uint32_t>(i + 48),
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT
});
const VkDescriptorSetLayoutCreateInfo layoutDesc{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,

View file

@ -0,0 +1,26 @@
#include "vk/helper/multiset.hpp"
#include "vk/core/descriptorpool.hpp"
#include "vk/core/shadermodule.hpp"
#include "vk/core/device.hpp"
#include <vector>
using namespace VK::Helper;
MultiSet MultiSetBuilder::build(
const Core::Device& device,
const Core::DescriptorPool& pool,
const Core::ShaderModule& shader) const {
MultiSet multiset{};
multiset.sets.reserve(this->recipes.size());
for (const auto& recipe : this->recipes)
multiset.sets.push_back({
.sets = Core::DescriptorSet(device, pool, shader,
recipe.inImages, recipe.outImages, this->samplers, this->buffer),
.readables = recipe.inImages,
.writables = recipe.outImages
});
return multiset;
}