From 1b367f7743a788a70d9ca2a1e95f27cfbeef4fb3 Mon Sep 17 00:00:00 2001 From: PancakeTAS Date: Mon, 1 Sep 2025 17:33:10 +0200 Subject: [PATCH] refactor: rework abstraction for descriptor sets --- framegen/.old/include/descriptorset.hpp | 128 ------------------ framegen/.old/src/core/descriptorset.cpp | 150 --------------------- framegen/include/vk/core/commandbuffer.hpp | 13 +- framegen/include/vk/core/descriptorset.hpp | 122 +++++++++++++++++ framegen/include/vk/core/device.hpp | 2 + framegen/src/vk/core/commandbuffer.cpp | 10 ++ framegen/src/vk/core/descriptorset.cpp | 93 +++++++++++++ 7 files changed, 238 insertions(+), 280 deletions(-) delete mode 100644 framegen/.old/include/descriptorset.hpp delete mode 100644 framegen/.old/src/core/descriptorset.cpp create mode 100644 framegen/include/vk/core/descriptorset.hpp create mode 100644 framegen/src/vk/core/descriptorset.cpp diff --git a/framegen/.old/include/descriptorset.hpp b/framegen/.old/include/descriptorset.hpp deleted file mode 100644 index 86fb20a..0000000 --- a/framegen/.old/include/descriptorset.hpp +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include "core/buffer.hpp" -#include "core/commandbuffer.hpp" -#include "core/descriptorpool.hpp" -#include "core/image.hpp" -#include "core/pipeline.hpp" -#include "core/sampler.hpp" -#include "core/shadermodule.hpp" -#include "core/device.hpp" - -#include - -#include -#include -#include -#include -#include - -namespace LSFG::Core { - - class DescriptorSetUpdateBuilder; - - /// - /// C++ wrapper class for a Vulkan descriptor set. - /// - /// This class manages the lifetime of a Vulkan descriptor set. - /// - class DescriptorSet { - public: - DescriptorSet() noexcept = default; - - /// - /// Create the descriptor set. - /// - /// @param device Vulkan device - /// @param pool Descriptor pool to allocate from - /// @param shaderModule Shader module to use for the descriptor set - /// - /// @throws LSFG::vulkan_error if object creation fails. - /// - DescriptorSet(const Core::Device& device, - const DescriptorPool& pool, const ShaderModule& shaderModule); - - /// - /// Update the descriptor set with resources. - /// - /// @param device Vulkan device - /// - [[nodiscard]] DescriptorSetUpdateBuilder update(const Core::Device& device) const; - - /// - /// Bind a descriptor set to a command buffer. - /// - /// @param commandBuffer Command buffer to bind the descriptor set to. - /// @param pipeline Pipeline to bind the descriptor set to. - /// - void bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const; - - /// Get the Vulkan handle. - [[nodiscard]] auto handle() const { return *this->descriptorSet; } - - /// Trivially copyable, moveable and destructible - DescriptorSet(const DescriptorSet&) noexcept = default; - DescriptorSet& operator=(const DescriptorSet&) noexcept = default; - DescriptorSet(DescriptorSet&&) noexcept = default; - DescriptorSet& operator=(DescriptorSet&&) noexcept = default; - ~DescriptorSet() = default; - private: - std::shared_ptr descriptorSet; - }; - - /// - /// Builder class for updating a descriptor set. - /// - class DescriptorSetUpdateBuilder { - friend class DescriptorSet; - public: - /// Add a resource to the descriptor set update. - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Image& image); - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Sampler& sampler); - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const Buffer& buffer); - DescriptorSetUpdateBuilder& add(VkDescriptorType type); // empty entry - - /// Add a list of resources to the descriptor set update. - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& images) { - for (const auto& image : images) this->add(type, image); return *this; } - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& samplers) { - for (const auto& sampler : samplers) this->add(type, sampler); return *this; } - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector& buffers) { - for (const auto& buffer : buffers) this->add(type, buffer); return *this; } - - /// Add an array of resources - template - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& images) { - for (const auto& image : images) this->add(type, image); return *this; } - template - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& samplers) { - for (const auto& sampler : samplers) this->add(type, sampler); return *this; } - template - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array& buffers) { - for (const auto& buffer : buffers) this->add(type, buffer); return *this; } - - /// Add an optional resource to the descriptor set update. - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& image) { - if (image.has_value()) this->add(type, *image); else this->add(type); return *this; } - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& sampler) { - if (sampler.has_value()) this->add(type, *sampler); else this->add(type); return *this; } - DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional& buffer) { - if (buffer.has_value()) this->add(type, *buffer); else this->add(type); return *this; } - - /// Finish building the descriptor set update. - void build(); - private: - const DescriptorSet* descriptorSet; - const Core::Device* device; - - DescriptorSetUpdateBuilder(const DescriptorSet& descriptorSet, const Core::Device& device) - : descriptorSet(&descriptorSet), device(&device) {} - - std::vector entries; - size_t bufferIdx{0}; - size_t samplerIdx{16}; - size_t inputIdx{32}; - size_t outputIdx{48}; - }; - -} diff --git a/framegen/.old/src/core/descriptorset.cpp b/framegen/.old/src/core/descriptorset.cpp deleted file mode 100644 index 62e9017..0000000 --- a/framegen/.old/src/core/descriptorset.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include - -#include "core/descriptorset.hpp" -#include "core/device.hpp" -#include "core/descriptorpool.hpp" -#include "core/shadermodule.hpp" -#include "core/commandbuffer.hpp" -#include "core/pipeline.hpp" -#include "core/image.hpp" -#include "core/sampler.hpp" -#include "core/buffer.hpp" -#include "common/exception.hpp" - -#include -#include -#include - -using namespace LSFG::Core; - -DescriptorSet::DescriptorSet(const Core::Device& device, - const DescriptorPool& pool, const ShaderModule& shaderModule) { - // create descriptor set - VkDescriptorSetLayout layout = shaderModule.getLayout(); - const VkDescriptorSetAllocateInfo desc{ - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .descriptorPool = pool.handle(), - .descriptorSetCount = 1, - .pSetLayouts = &layout - }; - VkDescriptorSet descriptorSetHandle{}; - auto res = vkAllocateDescriptorSets(device.handle(), &desc, &descriptorSetHandle); - if (res != VK_SUCCESS || descriptorSetHandle == VK_NULL_HANDLE) - throw LSFG::vulkan_error(res, "Unable to allocate descriptor set"); - - /// store set in shared ptr - this->descriptorSet = std::shared_ptr( - new VkDescriptorSet(descriptorSetHandle), - [dev = device.handle(), pool = pool](VkDescriptorSet* setHandle) { - vkFreeDescriptorSets(dev, pool.handle(), 1, setHandle); - } - ); -} - -DescriptorSetUpdateBuilder DescriptorSet::update(const Core::Device& device) const { - return { *this, device }; -} - -void DescriptorSet::bind(const CommandBuffer& commandBuffer, const Pipeline& pipeline) const { - VkDescriptorSet descriptorSetHandle = this->handle(); - vkCmdBindDescriptorSets(commandBuffer.handle(), - VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.getLayout(), - 0, 1, &descriptorSetHandle, 0, nullptr); -} - -// updater class - -DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Image& image) { - size_t* idx{type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE ? &this->outputIdx : &this->inputIdx}; - this->entries.push_back({ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = this->descriptorSet->handle(), - .dstBinding = static_cast(*idx), - .descriptorCount = 1, - .descriptorType = type, - .pImageInfo = new VkDescriptorImageInfo { - .imageView = image.getView(), - .imageLayout = VK_IMAGE_LAYOUT_GENERAL - }, - .pBufferInfo = nullptr - }); - (*idx)++; - return *this; -} - -DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Sampler& sampler) { - this->entries.push_back({ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = this->descriptorSet->handle(), - .dstBinding = static_cast(this->samplerIdx++), - .descriptorCount = 1, - .descriptorType = type, - .pImageInfo = new VkDescriptorImageInfo { - .sampler = sampler.handle(), - }, - .pBufferInfo = nullptr - }); - return *this; -} - -DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type, const Buffer& buffer) { - this->entries.push_back({ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = this->descriptorSet->handle(), - .dstBinding = static_cast(this->bufferIdx++), - .descriptorCount = 1, - .descriptorType = type, - .pImageInfo = nullptr, - .pBufferInfo = new VkDescriptorBufferInfo { - .buffer = buffer.handle(), - .range = buffer.getSize() - } - }); - return *this; -} - -DescriptorSetUpdateBuilder& DescriptorSetUpdateBuilder::add(VkDescriptorType type) { - size_t* idx{}; - switch (type) { - case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: - idx = &this->inputIdx; - break; - case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: - idx = &this->outputIdx; - break; - case VK_DESCRIPTOR_TYPE_SAMPLER: - idx = &this->samplerIdx; - break; - case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: - idx = &this->bufferIdx; - break; - default: - throw LSFG::vulkan_error(VK_ERROR_UNKNOWN, "Unsupported descriptor type"); - } - this->entries.push_back({ - .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, - .dstSet = this->descriptorSet->handle(), - .dstBinding = static_cast(*idx), - .descriptorCount = 1, - .descriptorType = type, - .pImageInfo = new VkDescriptorImageInfo { - }, - .pBufferInfo = nullptr - }); - (*idx)++; - return *this; -} - -void DescriptorSetUpdateBuilder::build() { - vkUpdateDescriptorSets(this->device->handle(), - static_cast(this->entries.size()), - this->entries.data(), 0, nullptr); - - // NOLINTBEGIN - for (const auto& entry : this->entries) { - delete entry.pImageInfo; - delete entry.pBufferInfo; - } - // NOLINTEND -} diff --git a/framegen/include/vk/core/commandbuffer.hpp b/framegen/include/vk/core/commandbuffer.hpp index 49c25aa..92a1a3a 100644 --- a/framegen/include/vk/core/commandbuffer.hpp +++ b/framegen/include/vk/core/commandbuffer.hpp @@ -1,6 +1,7 @@ #pragma once #include "vk/core/commandpool.hpp" +#include "vk/core/descriptorset.hpp" #include "vk/core/semaphore.hpp" #include "vk/core/pipeline.hpp" #include "vk/core/device.hpp" @@ -65,8 +66,16 @@ namespace VK::Core { /// void bindPipeline(const Pipeline& pipeline) const; - // TODO: Method for binding a descriptor set. - // TODO: Rework abstraction for descriptor sets. + /// + /// Bind a descriptor set to the command buffer. + /// + /// @param pipeline Vulkan compute pipeline + /// @param set Vulkan descriptor set + /// + /// @throws std::logic_error if the command buffer is not in Recording state + /// + void bindDescriptorSet(const Pipeline& pipeline, const DescriptorSet& set) const; + // TODO: Method for inserting a pipeline barrier. // TODO: Rework abstraction for barriers. // TODO: Method for copying a buffer to an image diff --git a/framegen/include/vk/core/descriptorset.hpp b/framegen/include/vk/core/descriptorset.hpp new file mode 100644 index 0000000..f99939d --- /dev/null +++ b/framegen/include/vk/core/descriptorset.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include "vk/core/descriptorpool.hpp" +#include "vk/core/shadermodule.hpp" +#include "vk/core/sampler.hpp" +#include "vk/core/device.hpp" +#include "vk/core/buffer.hpp" +#include "vk/core/image.hpp" + +#include + +#include +#include + +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; } + + /// Trivially copyable, moveable and destructible + ImageInfo(const ImageInfo&) noexcept = default; + ImageInfo& operator=(const ImageInfo&) noexcept = default; + ImageInfo(ImageInfo&&) noexcept = default; + ImageInfo& operator=(ImageInfo&&) noexcept = default; + ~ImageInfo() = default; + 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; } + + /// Trivially copyable, moveable and destructible + SamplerInfo(const SamplerInfo&) noexcept = default; + SamplerInfo& operator=(const SamplerInfo&) noexcept = default; + SamplerInfo(SamplerInfo&&) noexcept = default; + SamplerInfo& operator=(SamplerInfo&&) noexcept = default; + ~SamplerInfo() = default; + 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; } + + /// Trivially copyable, moveable and destructible + BufferInfo(const BufferInfo&) noexcept = default; + BufferInfo& operator=(const BufferInfo&) noexcept = default; + BufferInfo(BufferInfo&&) noexcept = default; + BufferInfo& operator=(BufferInfo&&) noexcept = default; + ~BufferInfo() = default; + private: + VkDescriptorBufferInfo info{}; + }; + + /// + /// C++ wrapper class for a Vulkan descriptor set. + /// + /// This class manages the lifetime of a Vulkan descriptor set. + /// + class DescriptorSet { + public: + DescriptorSet() noexcept = default; + + /// + /// Create the descriptor set. + /// + /// @param device Vulkan device + /// @param pool Descriptor pool to allocate from + /// @param shaderModule Shader module to use for the descriptor set + /// + /// @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); + + /// Get the Vulkan handle. + [[nodiscard]] auto handle() const { return *this->descriptorSet; } + + /// Trivially copyable, moveable and destructible + DescriptorSet(const DescriptorSet&) noexcept = default; + DescriptorSet& operator=(const DescriptorSet&) noexcept = default; + DescriptorSet(DescriptorSet&&) noexcept = default; + DescriptorSet& operator=(DescriptorSet&&) noexcept = default; + ~DescriptorSet() = default; + private: + std::shared_ptr descriptorSet; + }; + +} diff --git a/framegen/include/vk/core/device.hpp b/framegen/include/vk/core/device.hpp index bf2d85d..9c6aee1 100644 --- a/framegen/include/vk/core/device.hpp +++ b/framegen/include/vk/core/device.hpp @@ -20,6 +20,8 @@ namespace VK::Core { /// class Device { public: + Device() noexcept = default; + /// /// Create the device. /// diff --git a/framegen/src/vk/core/commandbuffer.cpp b/framegen/src/vk/core/commandbuffer.cpp index 34c7e13..a6cd7d0 100644 --- a/framegen/src/vk/core/commandbuffer.cpp +++ b/framegen/src/vk/core/commandbuffer.cpp @@ -62,6 +62,16 @@ void CommandBuffer::bindPipeline(const Pipeline& pipeline) const { vkCmdBindPipeline(*this->commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.handle()); } +void CommandBuffer::bindDescriptorSet(const Pipeline& pipeline, const DescriptorSet& set) const { + if (*this->state != CommandBufferState::Recording) + throw std::logic_error("Command buffer is not in Recording state"); + + VkDescriptorSet descriptorSetHandle = set.handle(); + vkCmdBindDescriptorSets(*this->commandBuffer, + VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.getLayout(), + 0, 1, &descriptorSetHandle, 0, nullptr); +} + void CommandBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z) const { if (*this->state != CommandBufferState::Recording) throw std::logic_error("Command buffer is not in Recording state"); diff --git a/framegen/src/vk/core/descriptorset.cpp b/framegen/src/vk/core/descriptorset.cpp new file mode 100644 index 0000000..9dc1cd3 --- /dev/null +++ b/framegen/src/vk/core/descriptorset.cpp @@ -0,0 +1,93 @@ +#include +#include +#include + +#include "vk/core/descriptorset.hpp" +#include "vk/core/device.hpp" +#include "vk/core/descriptorpool.hpp" +#include "vk/exception.hpp" + +#include + +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) { + // create descriptor set + VkDescriptorSetLayout layout = shaderModule.getLayout(); + const VkDescriptorSetAllocateInfo desc{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = pool.handle(), + .descriptorSetCount = 1, + .pSetLayouts = &layout + }; + VkDescriptorSet descriptorSetHandle{}; + auto res = vkAllocateDescriptorSets(device.handle(), &desc, &descriptorSetHandle); + 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); + + size_t bufferIdx{0}; + for (const auto& buf : uniformBuffers) + 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() + }); + + size_t samplerIdx{16}; + for (const auto& samp : samplers) + entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSetHandle, + .dstBinding = static_cast(samplerIdx++), + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER, + .pImageInfo = samp.handle() + }); + + size_t inputIdx{32}; + 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() + }); + + size_t outputIdx{48}; + for (const auto& img : storageImages) + entries.push_back({ + .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + .dstSet = descriptorSetHandle, + .dstBinding = static_cast(outputIdx++), + .descriptorCount = 1, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .pImageInfo = img.handle() + }); + + // update descriptor set + vkUpdateDescriptorSets(device.handle(), + static_cast(entries.size()), entries.data(), 0, nullptr); + + // store set in shared ptr + this->descriptorSet = std::shared_ptr( + new VkDescriptorSet(descriptorSetHandle), + [dev = device.handle(), pool = pool](VkDescriptorSet* setHandle) { + vkFreeDescriptorSets(dev, pool.handle(), 1, setHandle); + } + ); +}