refactor: rework abstraction for descriptor sets

This commit is contained in:
PancakeTAS 2025-09-01 17:33:10 +02:00
parent b86291b951
commit 1b367f7743
No known key found for this signature in database
7 changed files with 238 additions and 280 deletions

View file

@ -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 <vulkan/vulkan_core.h>
#include <vector>
#include <cstddef>
#include <array>
#include <optional>
#include <memory>
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<VkDescriptorSet> 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<Image>& images) {
for (const auto& image : images) this->add(type, image); return *this; }
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector<Sampler>& samplers) {
for (const auto& sampler : samplers) this->add(type, sampler); return *this; }
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::vector<Buffer>& buffers) {
for (const auto& buffer : buffers) this->add(type, buffer); return *this; }
/// Add an array of resources
template<std::size_t N>
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array<Image, N>& images) {
for (const auto& image : images) this->add(type, image); return *this; }
template<std::size_t N>
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array<Sampler, N>& samplers) {
for (const auto& sampler : samplers) this->add(type, sampler); return *this; }
template<std::size_t N>
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::array<Buffer, N>& 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>& image) {
if (image.has_value()) this->add(type, *image); else this->add(type); return *this; }
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional<Sampler>& sampler) {
if (sampler.has_value()) this->add(type, *sampler); else this->add(type); return *this; }
DescriptorSetUpdateBuilder& add(VkDescriptorType type, const std::optional<Buffer>& 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<VkWriteDescriptorSet> entries;
size_t bufferIdx{0};
size_t samplerIdx{16};
size_t inputIdx{32};
size_t outputIdx{48};
};
}

View file

@ -1,150 +0,0 @@
#include <volk.h>
#include <vulkan/vulkan_core.h>
#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 <cstddef>
#include <cstdint>
#include <memory>
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<VkDescriptorSet>(
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<uint32_t>(*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<uint32_t>(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<uint32_t>(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<uint32_t>(*idx),
.descriptorCount = 1,
.descriptorType = type,
.pImageInfo = new VkDescriptorImageInfo {
},
.pBufferInfo = nullptr
});
(*idx)++;
return *this;
}
void DescriptorSetUpdateBuilder::build() {
vkUpdateDescriptorSets(this->device->handle(),
static_cast<uint32_t>(this->entries.size()),
this->entries.data(), 0, nullptr);
// NOLINTBEGIN
for (const auto& entry : this->entries) {
delete entry.pImageInfo;
delete entry.pBufferInfo;
}
// NOLINTEND
}

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "vk/core/commandpool.hpp" #include "vk/core/commandpool.hpp"
#include "vk/core/descriptorset.hpp"
#include "vk/core/semaphore.hpp" #include "vk/core/semaphore.hpp"
#include "vk/core/pipeline.hpp" #include "vk/core/pipeline.hpp"
#include "vk/core/device.hpp" #include "vk/core/device.hpp"
@ -65,8 +66,16 @@ namespace VK::Core {
/// ///
void bindPipeline(const Pipeline& pipeline) const; 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: Method for inserting a pipeline barrier.
// TODO: Rework abstraction for barriers. // TODO: Rework abstraction for barriers.
// TODO: Method for copying a buffer to an image // TODO: Method for copying a buffer to an image

View file

@ -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 <vulkan/vulkan_core.h>
#include <memory>
#include <vector>
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<ImageInfo>& sampledImages,
const std::vector<ImageInfo>& storageImages,
const std::vector<SamplerInfo>& samplers,
const std::vector<BufferInfo>& 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<VkDescriptorSet> descriptorSet;
};
}

View file

@ -20,6 +20,8 @@ namespace VK::Core {
/// ///
class Device { class Device {
public: public:
Device() noexcept = default;
/// ///
/// Create the device. /// Create the device.
/// ///

View file

@ -62,6 +62,16 @@ void CommandBuffer::bindPipeline(const Pipeline& pipeline) const {
vkCmdBindPipeline(*this->commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.handle()); 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 { void CommandBuffer::dispatch(uint32_t x, uint32_t y, uint32_t z) const {
if (*this->state != CommandBufferState::Recording) if (*this->state != CommandBufferState::Recording)
throw std::logic_error("Command buffer is not in Recording state"); throw std::logic_error("Command buffer is not in Recording state");

View file

@ -0,0 +1,93 @@
#include <cstddef>
#include <volk.h>
#include <vulkan/vulkan_core.h>
#include "vk/core/descriptorset.hpp"
#include "vk/core/device.hpp"
#include "vk/core/descriptorpool.hpp"
#include "vk/exception.hpp"
#include <memory>
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) {
// 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<VkWriteDescriptorSet> 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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(outputIdx++),
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.pImageInfo = img.handle()
});
// update descriptor set
vkUpdateDescriptorSets(device.handle(),
static_cast<uint32_t>(entries.size()), entries.data(), 0, nullptr);
// store set in shared ptr
this->descriptorSet = std::shared_ptr<VkDescriptorSet>(
new VkDescriptorSet(descriptorSetHandle),
[dev = device.handle(), pool = pool](VkDescriptorSet* setHandle) {
vkFreeDescriptorSets(dev, pool.handle(), 1, setHandle);
}
);
}