DescriptorSetVk.h#

The DescriptorSet class, representing a “directory” of shader-accessible objects like Buffers and Images.

Includes#

  • PVRVk/BufferVk.h

  • PVRVk/DeviceVk.h

  • PVRVk/PipelineConfigVk.h

Included By#

Namespaces#

Classes#

Variables#

Source Code#

#pragma once
#include "PVRVk/DeviceVk.h"
#include "PVRVk/PipelineConfigVk.h"
#include "PVRVk/BufferVk.h"

namespace pvrvk {

const uint32_t descriptorTypeSize = 14; // Number elements in enum class DescriptorType

struct DescriptorSetLayoutCreateInfo
{
public:
    struct DescriptorSetLayoutBinding
    {
        uint16_t binding;
        pvrvk::DescriptorType descriptorType;
        uint16_t descriptorCount;
        pvrvk::ShaderStageFlags stageFlags;
        Sampler immutableSampler;
        DescriptorSetLayoutBinding() : descriptorCount(1), stageFlags(pvrvk::ShaderStageFlags::e_ALL), descriptorType(pvrvk::DescriptorType::e_MAX_ENUM) {}
        DescriptorSetLayoutBinding(uint16_t bindIndex, pvrvk::DescriptorType descType, uint16_t descriptorCount = 1,
            pvrvk::ShaderStageFlags stageFlags = pvrvk::ShaderStageFlags::e_ALL, const Sampler& immutableSampler = Sampler())
            : binding(bindIndex), descriptorType(descType), descriptorCount(descriptorCount), stageFlags(stageFlags), immutableSampler(immutableSampler)
        {}

        bool operator==(const DescriptorSetLayoutBinding& rhs) const
        {
            return binding == rhs.binding && descriptorType == rhs.descriptorType && descriptorCount == rhs.descriptorCount && stageFlags == rhs.stageFlags;
        }

        bool operator!=(const DescriptorSetLayoutBinding& rhs) const { return !(*this == rhs); }
    };

private:
    std::vector<DescriptorSetLayoutBinding> descLayoutInfo;

public:
    DescriptorSetLayoutCreateInfo() = default;
    DescriptorSetLayoutCreateInfo(std::initializer_list<DescriptorSetLayoutBinding> bindingList)
    {
        for (auto& it : bindingList) { setBinding(it); }
    }

    DescriptorSetLayoutCreateInfo& setBinding(const DescriptorSetLayoutBinding& layoutBinding)
    {
        auto it =
            std::find_if(descLayoutInfo.begin(), descLayoutInfo.end(), [&layoutBinding](const DescriptorSetLayoutBinding& info) { return info.binding == layoutBinding.binding; });
        if (it != descLayoutInfo.end()) { (*it) = layoutBinding; }
        else
        {
            descLayoutInfo.emplace_back(layoutBinding);
        }
        return *this;
    }

    DescriptorSetLayoutCreateInfo& setBinding(uint16_t binding, pvrvk::DescriptorType descriptorType, uint16_t descriptorCount = 1,
        pvrvk::ShaderStageFlags stageFlags = pvrvk::ShaderStageFlags::e_ALL, Sampler immutableSampler = Sampler())
    {
        return setBinding(DescriptorSetLayoutBinding(binding, descriptorType, descriptorCount, stageFlags, immutableSampler));
    }

    DescriptorSetLayoutCreateInfo& clear()
    {
        descLayoutInfo.clear();
        return *this;
    }

    uint16_t getNumBindings() const { return static_cast<uint16_t>(descLayoutInfo.size()); }

    bool operator==(const DescriptorSetLayoutCreateInfo& rhs) const
    {
        if (getNumBindings() != rhs.getNumBindings()) { return false; }
        for (uint32_t i = 0; i < getNumBindings(); ++i)
        {
            if (descLayoutInfo[i] != rhs.descLayoutInfo[i]) { return false; }
        }
        return true;
    }

    const DescriptorSetLayoutBinding* getBinding(uint16_t bindingId) const
    {
        auto it = std::find_if(descLayoutInfo.begin(), descLayoutInfo.end(), [&](const DescriptorSetLayoutBinding& info) { return info.binding == bindingId; });
        if (it != descLayoutInfo.end()) { return &(*it); }
        return nullptr;
    }

    const DescriptorSetLayoutBinding* getAllBindings() const { return descLayoutInfo.data(); }

private:
    friend class ::pvrvk::impl::DescriptorSetLayout_;
    friend struct ::pvrvk::WriteDescriptorSet;
};

namespace impl {
class DescriptorSetLayout_ : public PVRVkDeviceObjectBase<VkDescriptorSetLayout, ObjectType::e_DESCRIPTOR_SET_LAYOUT>, public DeviceObjectDebugUtils<DescriptorSetLayout_>
{
private:
    friend class Device_;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() = default;
        friend class DescriptorSetLayout_;
    };

    static DescriptorSetLayout constructShared(const DeviceWeakPtr& device, const DescriptorSetLayoutCreateInfo& createInfo)
    {
        return std::make_shared<DescriptorSetLayout_>(make_shared_enabler{}, device, createInfo);
    }

    DescriptorSetLayoutCreateInfo _createInfo;

public:
    DECLARE_NO_COPY_SEMANTICS(DescriptorSetLayout_)
    ~DescriptorSetLayout_();
    DescriptorSetLayout_(make_shared_enabler, const DeviceWeakPtr& device, const DescriptorSetLayoutCreateInfo& createInfo);

    const DescriptorSetLayoutCreateInfo& getCreateInfo() const { return _createInfo; }

    void clearCreateInfo() { _createInfo.clear(); }
};

template<typename T, uint32_t ArraySize>
class DescriptorStore
{
public:
    DescriptorStore() : _ptr(_tArray), _numItems(0) {}

    // Copy Constructor
    DescriptorStore(const DescriptorStore& descStore) : _numItems(descStore._numItems), _tVector(descStore._tVector)
    {
        // Copy _tArray elements
        std::copy(std::begin(descStore._tArray), std::end(descStore._tArray), _tArray);
        // Point towards _tVector.data() if _tVector is being used else point towards _tArray
        _ptr = !_tVector.empty() ? _tVector.data() : _tArray;
    }

    // Destructor
    ~DescriptorStore() = default;

    // Add swap functionality
    friend void swap(DescriptorStore& first, DescriptorStore& second)
    {
        using std::swap;

        // IF second._ptr is pointing to the array then point to MY array
        // If second._ptr is pointing to the vector then point to seconds vector
        T* tempPtr1 = second._ptr == second._tArray ? first._tArray : second._tVector.data();
        first._ptr = tempPtr1;

        // IF first._ptr is pointing to the array then point to MY array
        // If first._ptr is pointing to the vector then point to firsts vector
        T* tempPtr2 = first._ptr == first._tArray ? second._tArray : first._tVector.data();
        second._ptr = tempPtr2;

        swap(first._numItems, second._numItems);
        swap(first._tVector, second._tVector);
        swap(first._tArray, second._tArray);
    }

    // Assignment Operator
    DescriptorStore& operator=(DescriptorStore other)
    {
        swap(*this, other);
        return *this;
    }

    // Move Assignment Operator
    DescriptorStore& operator=(DescriptorStore&& other)
    {
        swap(other);
        return *this;
    }

    // Move constructor
    DescriptorStore(DescriptorStore&& other) noexcept : DescriptorStore() { swap(*this, other); }

    void clear()
    {
        // Reset each of the items
        std::fill(std::begin(_tArray), std::end(_tArray), T());
        _tVector.clear();
        _ptr = _tArray;
        _numItems = 0;
    }

    void set(uint32_t index, const T& obj)
    {
        // Move to overflow when the number of items reaches the limit of the limited storage array
        if (_tVector.empty() && index >= ArraySize)
        {
            // Move the limited storage array into the vector
            moveToOverFlow();
        }
        // Determine whether we should grow the container
        if (!_tVector.empty() && index >= _tVector.size())
        {
            // Grow by ArraySize to reduce the number of times we call resize
            _tVector.resize(index + ArraySize);
            _ptr = _tVector.data();
        }

        _numItems = std::max(_numItems, index + 1);

        _ptr[index] = obj;

        // Ensure _ptr is pointing at either _tArray or _tVector.data() depending on the index currently being set
        assert(index < ArraySize ? _ptr == _tArray : true && "Pointer must be pointing at _tArray");
        assert(index >= ArraySize ? _ptr == _tVector.data() : true && "Pointer must be pointing at _tVector.data()");
    }

    uint32_t size() const { return _numItems; }

    const T* begin() const { return _ptr; }
    const T* end() const { return _ptr + _numItems; }

    const T& operator[](uint32_t index) const { return get(index); }

    const T& get(uint32_t index) const { return _ptr[index]; }

private:
    void moveToOverFlow()
    {
        _tVector.reserve(ArraySize * 2);
        _tVector.assign(std::begin(_tArray), std::end(_tArray));
        // The pointer now points to the head of the vector
        _ptr = _tVector.data();
    }

    T _tArray[ArraySize];
    std::vector<T> _tVector;
    T* _ptr;
    uint32_t _numItems;
};
} // namespace impl

struct DescriptorPoolCreateInfo
{
private:
    std::array<pvrvk::DescriptorPoolSize, descriptorTypeSize> _descriptorPoolSizes;
    uint16_t _numDescriptors;
    uint16_t _maxSets;

public:
    DescriptorPoolCreateInfo() : _numDescriptors(0), _maxSets(200) {}

    explicit DescriptorPoolCreateInfo(uint16_t maxSets, uint16_t combinedImageSamplers = 32, uint16_t inputAttachments = 0, uint16_t staticUbos = 32, uint16_t dynamicUbos = 32,
        uint16_t staticSsbos = 0, uint16_t dynamicSsbos = 0)
        : _numDescriptors(0), _maxSets(maxSets)
    {
        if (combinedImageSamplers != 0) { addDescriptorInfo(pvrvk::DescriptorType::e_COMBINED_IMAGE_SAMPLER, combinedImageSamplers); }
        if (inputAttachments != 0) { addDescriptorInfo(pvrvk::DescriptorType::e_INPUT_ATTACHMENT, inputAttachments); }
        if (staticUbos != 0) { addDescriptorInfo(pvrvk::DescriptorType::e_UNIFORM_BUFFER, staticUbos); }
        if (dynamicUbos != 0) { addDescriptorInfo(pvrvk::DescriptorType::e_UNIFORM_BUFFER_DYNAMIC, dynamicUbos); }
        if (staticSsbos != 0) { addDescriptorInfo(pvrvk::DescriptorType::e_STORAGE_BUFFER, staticSsbos); }
        if (dynamicSsbos != 0) { addDescriptorInfo(pvrvk::DescriptorType::e_STORAGE_BUFFER_DYNAMIC, dynamicSsbos); }
    }

    explicit DescriptorPoolCreateInfo(std::initializer_list<pvrvk::DescriptorPoolSize> descriptorPoolSizes, uint16_t maxSets = 200) : _numDescriptors(0), _maxSets(maxSets)
    {
        for (auto& it : descriptorPoolSizes) { addDescriptorInfo(it); }
    }

    DescriptorPoolCreateInfo& addDescriptorInfo(const pvrvk::DescriptorPoolSize& descriptorPoolSize)
    {
        _descriptorPoolSizes[_numDescriptors] = descriptorPoolSize;
        _numDescriptors++;
        return *this;
    }

    DescriptorPoolCreateInfo& addDescriptorInfo(pvrvk::DescriptorType descType, uint16_t count) { return addDescriptorInfo(pvrvk::DescriptorPoolSize(descType, count)); }

    DescriptorPoolCreateInfo& setMaxDescriptorSets(uint16_t maxSets)
    {
        this->_maxSets = maxSets;
        return *this;
    }

    uint32_t getNumPoolSizes() const { return _numDescriptors; }

    const pvrvk::DescriptorPoolSize& getPoolSize(uint32_t index) const { return _descriptorPoolSizes[index]; }

    uint32_t getMaxDescriptorSets() const { return _maxSets; }
};

struct DescriptorImageInfo
{
    Sampler sampler;
    ImageView imageView;
    pvrvk::ImageLayout imageLayout;
    DescriptorImageInfo() : imageLayout(pvrvk::ImageLayout::e_UNDEFINED) {}

    explicit DescriptorImageInfo(const Sampler& sampler) : sampler(sampler), imageLayout(pvrvk::ImageLayout::e_UNDEFINED) {}

    DescriptorImageInfo(const ImageView& imageView, const Sampler& sampler, pvrvk::ImageLayout imageLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL)
        : sampler(sampler), imageView(imageView), imageLayout(imageLayout)
    {}

    DescriptorImageInfo(const ImageView& imageView, pvrvk::ImageLayout imageLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL)
        : imageView(imageView), imageLayout(imageLayout)
    {}
};
struct DescriptorBufferInfo
{
    Buffer buffer;
    VkDeviceSize offset;
    VkDeviceSize range;
    DescriptorBufferInfo() : offset(0), range(0) {}
    DescriptorBufferInfo(const Buffer& buffer, VkDeviceSize offset, VkDeviceSize range) : buffer(buffer), offset(offset), range(range) {}
};

struct WriteDescriptorSet
{
    WriteDescriptorSet() : _infos() {}

    WriteDescriptorSet(pvrvk::DescriptorType descType, DescriptorSet descSet, uint32_t dstBinding = 0, uint32_t dstArrayElement = 0)
        : _descType(descType), _descSet(descSet), _dstBinding(dstBinding), _dstArrayElement(dstArrayElement), _infos()
    {
        set(descType, descSet, dstBinding, dstArrayElement);
    }

    WriteDescriptorSet& setDescriptorType(pvrvk::DescriptorType descType)
    {
        _descType = descType;
        if ((_descType >= pvrvk::DescriptorType::e_SAMPLER && _descType <= pvrvk::DescriptorType::e_STORAGE_IMAGE) || _descType == pvrvk::DescriptorType::e_INPUT_ATTACHMENT)
        { _infoType = InfoType::ImageInfo; }
        else if (_descType >= pvrvk::DescriptorType::e_UNIFORM_BUFFER && _descType <= pvrvk::DescriptorType::e_STORAGE_BUFFER_DYNAMIC)
        {
            _infoType = InfoType::BufferInfo;
        }
        else if (_descType == pvrvk::DescriptorType::e_UNIFORM_TEXEL_BUFFER || _descType == pvrvk::DescriptorType::e_STORAGE_TEXEL_BUFFER)
        {
            _infoType = InfoType::TexelBufferView;
        }
        else if (_descType == pvrvk::DescriptorType::e_ACCELERATION_STRUCTURE_KHR)
        {
            _infoType = InfoType::AccelerationStructureInfo;
        }
        else
        {
            assert(false && "Cannot resolve Info type from descriptor type");
        }
        return *this;
    }

    WriteDescriptorSet& setDescriptorSet(DescriptorSet& descriptorSet)
    {
        _descSet = descriptorSet;
        return *this;
    }

    WriteDescriptorSet& setDestBinding(uint32_t binding)
    {
        _dstBinding = binding;
        return *this;
    }

    WriteDescriptorSet& setDestArrayElement(uint32_t arrayElement)
    {
        _dstArrayElement = arrayElement;
        return *this;
    }

    WriteDescriptorSet& set(pvrvk::DescriptorType newDescType, const DescriptorSet& descSet, uint32_t dstBinding = 0, uint32_t dstArrayElement = 0)
    {
        setDescriptorType(newDescType);
        _descSet = descSet;
        _dstBinding = dstBinding;
        _dstArrayElement = dstArrayElement;
        _infos.clear();
        return *this;
    }

    WriteDescriptorSet& setImageInfo(uint32_t arrayIndex, const DescriptorImageInfo& imageInfo)
    {
#ifdef DEBUG
        // VALIDATE DESCRIPTOR TYPE
        assert(((_descType >= pvrvk::DescriptorType::e_SAMPLER) && (_descType <= pvrvk::DescriptorType::e_STORAGE_IMAGE)) || (_descType == pvrvk::DescriptorType::e_INPUT_ATTACHMENT));
        if (_descType == pvrvk::DescriptorType::e_COMBINED_IMAGE_SAMPLER) { assert(imageInfo.sampler && imageInfo.imageView && "Sampler and ImageView must be valid"); }
#endif
        DescriptorInfos info;
        info.imageInfo = imageInfo;
        _infos.set(arrayIndex, info);
        return *this;
    }

    WriteDescriptorSet& setBufferInfo(uint32_t arrayIndex, const DescriptorBufferInfo& bufferInfo)
    {
#ifdef DEBUG
        assert(_descType >= pvrvk::DescriptorType::e_UNIFORM_BUFFER && _descType <= pvrvk::DescriptorType::e_STORAGE_BUFFER_DYNAMIC);
        assert(bufferInfo.buffer && "Buffer must be valid");
#endif
        DescriptorInfos info;
        info.bufferInfo = bufferInfo;
        _infos.set(arrayIndex, info);
        return *this;
    }

    WriteDescriptorSet& setAccelerationStructureInfo(uint32_t arrayIndex, const AccelerationStructure& accelerationStructure)
    {
#ifdef DEBUG
        assert(_descType == pvrvk::DescriptorType::e_ACCELERATION_STRUCTURE_KHR);
        assert(accelerationStructure && "Acceleration Structure must be valid");
#endif
        DescriptorInfos info;
        info.accelerationStructure = accelerationStructure;
        _infos.set(arrayIndex, info);
        return *this;
    }

    WriteDescriptorSet& setTexelBufferInfo(uint32_t arrayIndex, const BufferView& bufferView)
    {
#ifdef DEBUG
        assert(_descType >= pvrvk::DescriptorType::e_UNIFORM_TEXEL_BUFFER && _descType <= pvrvk::DescriptorType::e_STORAGE_TEXEL_BUFFER);
        assert(bufferView && "Texel BufferView must be valid");
#endif
        DescriptorInfos info;
        info.texelBuffer = bufferView;
        _infos.set(arrayIndex, info);
        return *this;
    }

    WriteDescriptorSet& clearAllInfos()
    {
        _infos.clear();
        return *this;
    }

    uint32_t getNumDescriptors() const { return _infos.size(); }

    pvrvk::DescriptorType getDescriptorType() const { return _descType; }

    DescriptorSet getDescriptorSet() { return _descSet; }

    const DescriptorSet& getDescriptorSet() const { return _descSet; }

    uint32_t getDestArrayElement() const { return _dstArrayElement; }

    uint32_t getDestBinding() const { return _dstBinding; }

private:
    friend class ::pvrvk::impl::Device_;

    pvrvk::DescriptorType _descType;
    DescriptorSet _descSet;
    uint32_t _dstBinding;
    uint32_t _dstArrayElement;
    struct DescriptorInfos
    {
        DescriptorImageInfo imageInfo;
        DescriptorBufferInfo bufferInfo;
        BufferView texelBuffer;
        AccelerationStructure accelerationStructure;

        DescriptorInfos() = default;
        bool isValid() const { return imageInfo.imageView || imageInfo.sampler || bufferInfo.buffer || texelBuffer || accelerationStructure; }
    };

    impl::DescriptorStore<DescriptorInfos, 4> _infos;

    enum InfoType
    {
        ImageInfo,
        BufferInfo,
        TexelBufferView,
        AccelerationStructureInfo
    };
    InfoType _infoType;

    // CALL THIS ONE FROM THE DEVICE - CPU SIDE KEEPING ALIVE OF THE DESCRIPTORS IN THIS SET
    void updateKeepAliveIntoDestinationDescriptorSet() const;
};

struct CopyDescriptorSet
{
    DescriptorSet srcSet;
    uint32_t srcBinding;
    uint32_t srcArrayElement;
    DescriptorSet dstSet;
    uint32_t dstBinding;
    uint32_t dstArrayElement;
    uint32_t descriptorCount;
};

namespace impl {
class DescriptorPool_ : public PVRVkDeviceObjectBase<VkDescriptorPool, ObjectType::e_DESCRIPTOR_POOL>,
                        public DeviceObjectDebugUtils<DescriptorPool_>,
                        public std::enable_shared_from_this<DescriptorPool_>
{
private:
    friend class Device_;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() = default;
        friend class DescriptorPool_;
    };

    static DescriptorPool constructShared(const DeviceWeakPtr& device, const DescriptorPoolCreateInfo& createInfo)
    {
        return std::make_shared<DescriptorPool_>(make_shared_enabler{}, device, createInfo);
    }

    DescriptorPoolCreateInfo _createInfo;

public:
    DECLARE_NO_COPY_SEMANTICS(DescriptorPool_)
    ~DescriptorPool_();
    DescriptorPool_(make_shared_enabler, const DeviceWeakPtr& device, const DescriptorPoolCreateInfo& createInfo);

    DescriptorSet allocateDescriptorSet(const DescriptorSetLayout& layout);

    const DescriptorPoolCreateInfo& getCreateInfo() const { return _createInfo; }
};

class DescriptorSet_ : public PVRVkDeviceObjectBase<VkDescriptorSet, ObjectType::e_DESCRIPTOR_SET>, public DeviceObjectDebugUtils<DescriptorSet_>
{
private:
    friend struct ::pvrvk::WriteDescriptorSet;
    friend class DescriptorPool_;

    class make_shared_enabler
    {
    protected:
        make_shared_enabler() = default;
        friend class DescriptorSet_;
    };

    static DescriptorSet constructShared(const DescriptorSetLayout& descSetLayout, DescriptorPool& pool)
    {
        return std::make_shared<DescriptorSet_>(make_shared_enabler{}, descSetLayout, pool);
    }

    mutable std::vector<std::vector<std::shared_ptr<void> > > _keepAlive;
    DescriptorSetLayout _descSetLayout;
    DescriptorPool _descPool;

public:
    DECLARE_NO_COPY_SEMANTICS(DescriptorSet_)

    DescriptorSet_(make_shared_enabler, const DescriptorSetLayout& descSetLayout, DescriptorPool& pool)
        : PVRVkDeviceObjectBase(pool->getDevice()), DeviceObjectDebugUtils(), _descSetLayout(descSetLayout), _descPool(pool)
    {
        // Create the Vulkan VkDescriptorSetAllocateInfo structure
        VkDescriptorSetAllocateInfo allocInfo = {};
        allocInfo.sType = static_cast<VkStructureType>(pvrvk::StructureType::e_DESCRIPTOR_SET_ALLOCATE_INFO);
        allocInfo.pSetLayouts = &getDescriptorSetLayout()->getVkHandle();
        allocInfo.descriptorSetCount = 1;
        allocInfo.descriptorPool = getDescriptorPool()->getVkHandle();

        // For appropriate smart reference counting we need to keep alive the bindings
        const auto& allBindings = _descSetLayout->getCreateInfo().getAllBindings();
        uint16_t maxBinding = 0;
        uint32_t i = 0, size = _descSetLayout->getCreateInfo().getNumBindings();
        // Loop through the descriptor set bindings and determine the maximum binding
        for (; i < size; ++i) { maxBinding = std::max(allBindings[i].binding, maxBinding); }
        // Use the maximum binding + 1 to resize the keepAlive array
        _keepAlive.resize(maxBinding + 1u);
        // Now use the descriptor count for each descriptor binding to determine the total number of entries
        for (i = 0; i < size; ++i)
        {
            auto& entry = allBindings[i];
            auto& aliveEntry = _keepAlive[entry.binding];
            aliveEntry.resize(entry.descriptorCount);
        }
        vkThrowIfFailed(getDevice()->getVkBindings().vkAllocateDescriptorSets(_descSetLayout->getDevice()->getVkHandle(), &allocInfo, &_vkHandle), "Allocate Descriptor Set failed");
    }

    ~DescriptorSet_()
    {
        _keepAlive.clear();
        if (getVkHandle() != VK_NULL_HANDLE)
        {
            if (getDescriptorPool()->getDevice())
            {
                getDevice()->getVkBindings().vkFreeDescriptorSets(getDescriptorPool()->getDevice()->getVkHandle(), getDescriptorPool()->getVkHandle(), 1, &getVkHandle());
                _vkHandle = VK_NULL_HANDLE;
            }
            else
            {
                reportDestroyedAfterDevice();
            }
            _descSetLayout.reset();
        }
    }

    const DescriptorSetLayout& getDescriptorSetLayout() const { return _descSetLayout; }

    const DescriptorPool& getDescriptorPool() const { return _descPool; }

    DescriptorPool& getDescriptorPool() { return _descPool; }
};

} // namespace impl

// For smart pointer reference counting we need to keep alive the descriptor set binding entries to do this we place them in an array kept alive by the DescriptorSet itself
// This means that the caller application can let resources go out scope and the Descriptor set "keepAlive" array will keep them alive as long as needed
inline void WriteDescriptorSet::updateKeepAliveIntoDestinationDescriptorSet() const
{
    // Get the keep alive entry for the current binding
    auto& keepAlive = getDescriptorSet()->_keepAlive[this->_dstBinding];

    // Handle BufferInfo entries
    if (_infoType == InfoType::BufferInfo)
    {
        for (uint32_t i = 0; i < _infos.size(); ++i)
        {
            // Ensure the into entry is valid
            if (_infos[i].isValid()) { keepAlive[i] = _infos[i].bufferInfo.buffer; }
        }
    }
    // Handle ImageInfo entries
    else if (_infoType == InfoType::ImageInfo)
    {
        for (uint32_t i = 0; i < _infos.size(); ++i)
        {
            // Ensure the into entry is valid
            if (_infos[i].isValid())
            {
                auto newpair = std::make_shared<std::pair<Sampler, ImageView> >();
                newpair->first = _infos[i].imageInfo.sampler;
                newpair->second = _infos[i].imageInfo.imageView;

                keepAlive[i] = newpair;
            }
        }
    }
    // Handle TexelBufferView entries
    else if (_infoType == InfoType::TexelBufferView)
    {
        for (uint32_t i = 0; i < _infos.size(); ++i)
        {
            // Ensure the into entry is valid
            if (_infos[i].isValid()) { keepAlive[i] = _infos[i].texelBuffer; }
        }
    }
}
} // namespace pvrvk