From 32d35c25fc123d8372db7ddfed95538dcc20f8ea Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 1 Sep 2025 21:32:20 +0200 Subject: [PATCH] refactor: add multiset helper --- framegen/include/vk/core/commandbuffer.hpp | 4 +- framegen/include/vk/core/descriptorset.hpp | 60 +------ framegen/include/vk/core/shadermodule.hpp | 11 +- framegen/include/vk/helper/multiset.hpp | 195 +++++++++++++++++++++ framegen/src/vk/core/commandbuffer.cpp | 16 +- framegen/src/vk/core/descriptorset.cpp | 54 ++++-- framegen/src/vk/core/shadermodule.cpp | 68 +++---- framegen/src/vk/helper/multiset.cpp | 26 +++ 8 files changed, 322 insertions(+), 112 deletions(-) create mode 100644 framegen/include/vk/helper/multiset.hpp create mode 100644 framegen/src/vk/helper/multiset.cpp diff --git a/framegen/include/vk/core/commandbuffer.hpp b/framegen/include/vk/core/commandbuffer.hpp index 00eb20a..a52f2cb 100644 --- a/framegen/include/vk/core/commandbuffer.hpp +++ b/framegen/include/vk/core/commandbuffer.hpp @@ -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& readableImages, - const std::vector& writableImages) const; + const std::vector>& readableImages, + const std::vector& writableImages) const; /// /// Copy a buffer to an image. diff --git a/framegen/include/vk/core/descriptorset.hpp b/framegen/include/vk/core/descriptorset.hpp index 396600f..9db2deb 100644 --- a/framegen/include/vk/core/descriptorset.hpp +++ b/framegen/include/vk/core/descriptorset.hpp @@ -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& sampledImages, - const std::vector& storageImages, - const std::vector& samplers, - const std::vector& uniformBuffers); + const std::vector>& sampledImages, + const std::vector& storageImages, + const std::vector& samplers, + const std::optional& buffer); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->descriptorSet; } diff --git a/framegen/include/vk/core/shadermodule.hpp b/framegen/include/vk/core/shadermodule.hpp index b738083..8d4d716 100644 --- a/framegen/include/vk/core/shadermodule.hpp +++ b/framegen/include/vk/core/shadermodule.hpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -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& code, - const std::vector>& descriptorTypes); + size_t sampledImages, + size_t storageImages, + size_t buffers, + size_t samplers); /// Get the Vulkan handle. [[nodiscard]] auto handle() const { return *this->shaderModule; } diff --git a/framegen/include/vk/helper/multiset.hpp b/framegen/include/vk/helper/multiset.hpp new file mode 100644 index 0000000..c071ea3 --- /dev/null +++ b/framegen/include/vk/helper/multiset.hpp @@ -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 +#include +#include + +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> readables; + std::vector writables; + }; + std::vector 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& 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& 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& 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 buffer; + std::vector samplers; + struct SetRecipe { + std::vector> inImages; + std::vector outImages; + }; + std::vector recipes; + }; + +} diff --git a/framegen/src/vk/core/commandbuffer.cpp b/framegen/src/vk/core/commandbuffer.cpp index 50c151e..b7acf93 100644 --- a/framegen/src/vk/core/commandbuffer.cpp +++ b/framegen/src/vk/core/commandbuffer.cpp @@ -106,8 +106,8 @@ void CommandBuffer::insertBarrier( } void CommandBuffer::insertBarrier( - const std::vector& readableImages, - const std::vector& writableImages) const { + const std::vector>& readableImages, + const std::vector& 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 barriers(totalImages); + const size_t totalImages = readableImages.size() + writableImages.size(); + std::vector 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 diff --git a/framegen/src/vk/core/descriptorset.cpp b/framegen/src/vk/core/descriptorset.cpp index 23d682a..955ebde 100644 --- a/framegen/src/vk/core/descriptorset.cpp +++ b/framegen/src/vk/core/descriptorset.cpp @@ -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 #include #include #include @@ -16,10 +20,10 @@ using namespace VK::Core; DescriptorSet::DescriptorSet(const Device& device, const DescriptorPool& pool, const ShaderModule& shaderModule, - const std::vector& sampledImages, - const std::vector& storageImages, - const std::vector& samplers, - const std::vector& uniformBuffers) { + const std::vector>& sampledImages, + const std::vector& storageImages, + const std::vector& samplers, + const std::optional& 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 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 entries; + entries.reserve(bindingCount); + + std::optional bufferInfos; + + if (buffer.has_value()) entries.push_back({ .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = descriptorSetHandle, - .dstBinding = static_cast(bufferIdx++), .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - .pBufferInfo = buf.handle() + .pBufferInfo = &(bufferInfos = VkDescriptorBufferInfo{ + .buffer = buffer->handle(), + .range = buffer->getSize() + }).value() }); + std::vector 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(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(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(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 diff --git a/framegen/src/vk/core/shadermodule.cpp b/framegen/src/vk/core/shadermodule.cpp index 7409f32..60dd683 100644 --- a/framegen/src/vk/core/shadermodule.cpp +++ b/framegen/src/vk/core/shadermodule.cpp @@ -7,14 +7,16 @@ #include #include -#include #include #include using namespace VK::Core; ShaderModule::ShaderModule(const Device& device, const std::vector& code, - const std::vector>& 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& cod // create descriptor set layout std::vector 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(*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(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(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(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(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, diff --git a/framegen/src/vk/helper/multiset.cpp b/framegen/src/vk/helper/multiset.cpp new file mode 100644 index 0000000..38336a9 --- /dev/null +++ b/framegen/src/vk/helper/multiset.cpp @@ -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 + +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; +}