Browse Source

Vk: implement descriptor set allocation, freeing and pool reset.

pull/504/head
Vladimír Vondruš 5 years ago
parent
commit
412d54b25b
  1. 62
      doc/snippets/MagnumVk.cpp
  2. 10
      doc/vulkan-mapping.dox
  3. 2
      doc/vulkan-support.dox
  4. 2
      src/Magnum/Vk/CMakeLists.txt
  5. 92
      src/Magnum/Vk/DescriptorPool.cpp
  6. 120
      src/Magnum/Vk/DescriptorPool.h
  7. 9
      src/Magnum/Vk/DescriptorPoolCreateInfo.h
  8. 69
      src/Magnum/Vk/DescriptorSet.cpp
  9. 173
      src/Magnum/Vk/DescriptorSet.h
  10. 11
      src/Magnum/Vk/DescriptorSetLayout.h
  11. 7
      src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h
  12. 7
      src/Magnum/Vk/Result.h
  13. 6
      src/Magnum/Vk/Test/CMakeLists.txt
  14. 228
      src/Magnum/Vk/Test/DescriptorPoolVkTest.cpp
  15. 62
      src/Magnum/Vk/Test/DescriptorSetTest.cpp
  16. 110
      src/Magnum/Vk/Test/DescriptorSetVkTest.cpp
  17. 1
      src/Magnum/Vk/Vk.h

62
doc/snippets/MagnumVk.cpp

@ -25,6 +25,7 @@
#include <string>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/Directory.h>
@ -40,6 +41,7 @@
#include "Magnum/Vk/CommandPoolCreateInfo.h"
#include "Magnum/Vk/ComputePipelineCreateInfo.h"
#include "Magnum/Vk/DescriptorPoolCreateInfo.h"
#include "Magnum/Vk/DescriptorSet.h"
#include "Magnum/Vk/DescriptorSetLayoutCreateInfo.h"
#include "Magnum/Vk/DescriptorType.h"
#include "Magnum/Vk/DeviceCreateInfo.h"
@ -331,6 +333,66 @@ Vk::DescriptorPool pool{device, Vk::DescriptorPoolCreateInfo{8, {
/* [DescriptorPool-creation] */
}
{
/* [DescriptorSet-allocation] */
Vk::DescriptorSetLayout layout{DOXYGEN_IGNORE(NoCreate)};
Vk::DescriptorPool pool{DOXYGEN_IGNORE(NoCreate)};
Vk::DescriptorSet set = pool.allocate(layout);
/* [DescriptorSet-allocation] */
}
{
Vk::DescriptorSetLayout layout{NoCreate};
Vk::DescriptorPool pool{NoCreate}, overflowPool{NoCreate};
/* [DescriptorSet-allocation-try] */
Containers::Optional<Vk::DescriptorSet> set = pool.tryAllocate(layout);
/* Oops, the pool is full (or fragmented). Hope the plan B doesn't fail too. */
if(!set) set = overflowPool.allocate(layout);
/* [DescriptorSet-allocation-try] */
}
{
Vk::Device device{NoCreate};
Vk::DescriptorSetLayout layout{NoCreate};
/* [DescriptorSet-allocation-free] */
Vk::DescriptorPool pool{device, Vk::DescriptorPoolCreateInfo{DOXYGEN_IGNORE(0), {
DOXYGEN_IGNORE()
}, Vk::DescriptorPoolCreateInfo::Flag::FreeDescriptorSet}};
{
Vk::DescriptorSet set = pool.allocate(layout);
// the set gets automatically freed at the end of scope
}
/* [DescriptorSet-allocation-free] */
}
{
Vk::Instance instance{NoCreate};
Vk::DescriptorPool pool{NoCreate};
/* [DescriptorSet-allocation-variable] */
Vk::Device device{instance, Vk::DeviceCreateInfo{DOXYGEN_IGNORE(Vk::pickDevice(instance))}
DOXYGEN_IGNORE()
.addEnabledExtensions<Vk::Extensions::EXT::descriptor_indexing>()
.setEnabledFeatures(
Vk::DeviceFeature::DescriptorBindingVariableDescriptorCount|
DOXYGEN_IGNORE(Vk::DeviceFeatures{})
)
};
Vk::DescriptorSetLayout layout{device, Vk::DescriptorSetLayoutCreateInfo{
{{DOXYGEN_IGNORE(0), Vk::DescriptorType::SampledImage, 8,
Vk::ShaderStage::Fragment,
Vk::DescriptorSetLayoutBinding::Flag::VariableDescriptorCount}},
DOXYGEN_IGNORE()
}};
Vk::DescriptorSet set = pool.allocate(layout, 4);
/* [DescriptorSet-allocation-variable] */
}
{
Vk::Device device{NoCreate};
/* The include should be a no-op here since it was already included above */

10
doc/vulkan-mapping.dox

@ -59,7 +59,7 @@ Vulkan handle | Matching API
@type_vk{DebugReportCallbackEXT} @m_class{m-label m-danger} **deprecated** @m_class{m-label m-flat m-warning} **EXT** | |
@type_vk{DebugUtilsMessengerEXT} @m_class{m-label m-flat m-warning} **EXT** | |
@type_vk{DescriptorPool} | @ref DescriptorPool
@type_vk{DescriptorSet} | |
@type_vk{DescriptorSet} | @ref DescriptorSet
@type_vk{DescriptorSetLayout} | @ref DescriptorSetLayout
@type_vk{DescriptorUpdateTemplate} @m_class{m-label m-flat m-success} **KHR, 1.1** | |
@type_vk{Device} | @ref Device
@ -91,7 +91,7 @@ Vulkan handle | Matching API
Vulkan function | Matching API
--------------------------------------- | ------------
@fn_vk{AllocateCommandBuffers}, \n @fn_vk{FreeCommandBuffers} | @ref CommandPool::allocate(), @ref CommandBuffer destructor
@fn_vk{AllocateDescriptorSets}, \n @fn_vk{FreeDescriptorSets} | |
@fn_vk{AllocateDescriptorSets}, \n @fn_vk{FreeDescriptorSets} | @ref DescriptorPool::allocate(), @ref DescriptorSet destructor
@fn_vk{AllocateMemory}, \n @fn_vk{FreeMemory} | @ref Memory constructor and destructor
@subsection vulkan-mapping-functions-b B
@ -325,7 +325,7 @@ Vulkan function | Matching API
--------------------------------------- | ------------
@fn_vk{ResetCommandBuffer} | @ref CommandBuffer::reset()
@fn_vk{ResetCommandPool} | @ref CommandPool::reset()
@fn_vk{ResetDescriptorPool} | |
@fn_vk{ResetDescriptorPool} | @ref DescriptorPool::reset()
@fn_vk{ResetFences} | @ref Fence::reset()
@fn_vk{ResetQueryPool} @m_class{m-label m-flat m-success} **EXT, 1.2** | |
@ -465,12 +465,12 @@ Vulkan structure | Matching API
@type_vk{DescriptorImageInfo} | |
@type_vk{DescriptorPoolCreateInfo} | @ref DescriptorPoolCreateInfo
@type_vk{DescriptorPoolSize} | @ref DescriptorPoolCreateInfo
@type_vk{DescriptorSetAllocateInfo} | |
@type_vk{DescriptorSetAllocateInfo} | not exposed, internal to @ref DescriptorPool::allocate(VkDescriptorSetLayout)
@type_vk{DescriptorSetLayoutBinding} | @ref DescriptorSetLayoutBinding
@type_vk{DescriptorSetLayoutBindingFlagsCreateInfo} @m_class{m-label m-flqat m-success} **EXT, 1.2** | @ref DescriptorSetLayoutCreateInfo
@type_vk{DescriptorSetLayoutCreateInfo} | @ref DescriptorSetLayoutCreateInfo
@type_vk{DescriptorSetLayoutSupport} @m_class{m-label m-flat m-success} **KHR, 1.1** | |
@type_vk{DescriptorSetVariableDescriptorCountAllocateInfo} @m_class{m-label m-flat m-success} **EXT, 1.2** | |
@type_vk{DescriptorSetVariableDescriptorCountAllocateInfo} @m_class{m-label m-flat m-success} **EXT, 1.2** | not exposed, internal to @ref DescriptorPool::allocate(VkDescriptorSetLayout, UnsignedInt)
@type_vk{DescriptorSetVariableDescriptorCountLayoutSupport} @m_class{m-label m-flat m-success} **EXT, 1.2** | |
@type_vk{DescriptorUpdateTemplateEntry} @m_class{m-label m-flat m-success} **KHR, 1.1** | |
@type_vk{DescriptorUpdateTemplateCreateInfo} @m_class{m-label m-flat m-success} **KHR, 1.1** | |

2
doc/vulkan-support.dox

@ -90,7 +90,7 @@ Extension | Status
@vk_extension{KHR,create_renderpass2} | done
@vk_extension{EXT,sampler_filter_minmax} | |
@vk_extension{KHR,image_format_list} | |
@vk_extension{EXT,descriptor_indexing} | done except properties and variable descriptor count allocation
@vk_extension{EXT,descriptor_indexing} | done except properties
@vk_extension{EXT,shader_viewport_index_layer} | |
@vk_extension{KHR,draw_indirect_count} | |
@vk_extension{KHR,shader_subgroup_extended_types} | |

2
src/Magnum/Vk/CMakeLists.txt

@ -29,6 +29,7 @@ find_package(Vulkan REQUIRED)
set(MagnumVk_SRCS
CommandBuffer.cpp
CommandPool.cpp
DescriptorSet.cpp
DescriptorSetLayout.cpp
DescriptorType.cpp
Extensions.cpp
@ -77,6 +78,7 @@ set(MagnumVk_HEADERS
ComputePipelineCreateInfo.h
DescriptorPool.h
DescriptorPoolCreateInfo.h
DescriptorSet.h
DescriptorSetLayout.h
DescriptorSetLayoutCreateInfo.h
DescriptorType.h

92
src/Magnum/Vk/DescriptorPool.cpp

@ -27,8 +27,10 @@
#include "DescriptorPoolCreateInfo.h"
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/Optional.h>
#include "Magnum/Vk/Assert.h"
#include "Magnum/Vk/DescriptorSet.h"
#include "Magnum/Vk/DescriptorType.h"
#include "Magnum/Vk/Device.h"
#include "Magnum/Vk/Result.h"
@ -99,13 +101,13 @@ DescriptorPool DescriptorPool::wrap(Device& device, const VkDescriptorPool handl
return out;
}
DescriptorPool::DescriptorPool(Device& device, const DescriptorPoolCreateInfo& info): _device{&device}, _flags{HandleFlag::DestroyOnDestruction} {
DescriptorPool::DescriptorPool(Device& device, const DescriptorPoolCreateInfo& info): _device{&device}, _flags{HandleFlag::DestroyOnDestruction}, _freeAllocatedSets{!!(info->flags & VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT)} {
MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(device->CreateDescriptorPool(device, info, nullptr, &_handle));
}
DescriptorPool::DescriptorPool(NoCreateT): _device{}, _handle{} {}
DescriptorPool::DescriptorPool(DescriptorPool&& other) noexcept: _device{other._device}, _handle{other._handle}, _flags{other._flags} {
DescriptorPool::DescriptorPool(DescriptorPool&& other) noexcept: _device{other._device}, _handle{other._handle}, _flags{other._flags}, _freeAllocatedSets{other._freeAllocatedSets} {
other._handle = {};
}
@ -119,9 +121,95 @@ DescriptorPool& DescriptorPool::operator=(DescriptorPool&& other) noexcept {
swap(other._device, _device);
swap(other._handle, _handle);
swap(other._flags, _flags);
swap(other._freeAllocatedSets, _freeAllocatedSets);
return *this;
}
std::pair<Result, DescriptorSet> DescriptorPool::allocateInternal(const VkDescriptorSetLayout layout) {
DescriptorSet set{NoCreate};
set._device = _device;
set._pool = _handle;
set._flags = _freeAllocatedSets ? HandleFlag::DestroyOnDestruction : HandleFlags{};
VkDescriptorSetAllocateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
info.descriptorPool = _handle;
info.descriptorSetCount = 1;
info.pSetLayouts = &layout;
/* VK_ERROR_OUT_OF_POOL_MEMORY is only available since VK_KHR_maintenance1
and it's not really clear what was supposed to happen before that. At
the very least, running the allocateFail() test using
./VkDescriptorPoolVkTest --magnum-vulkan-version 1.0 --magnum-enable-layers VK_LAYER_KHRONOS_validation
without VK_KHR_maintenance1 enabled, the validation layer complains that
I'm allocating from a pool that doesn't have enough free items, which
implies it used to be a user error and thus the driver is free to do
*anything*, including random crashes, as noted on
https://community.khronos.org/t/descriptor-pool-able-to-allocate-even-though-pool-should-be-empty/7330/6
From practical testing, even the oldest Vulkan driver I have (ARM Mali
on Huawei P9, Vulkan 1.0.66) seems to return VK_ERROR_OUT_OF_POOL_MEMORY
no matter whether the extension is enabled or not. So I'll assume all
contemporary drivers in early 2021 do this, there's nothing I can do
otherwise. */
const Result result = MAGNUM_VK_INTERNAL_ASSERT_SUCCESS_OR((**_device).AllocateDescriptorSets(*_device, &info, &set._handle), Result::ErrorOutOfPoolMemory, Result::ErrorFragmentedPool);
return {result, std::move(set)};
}
DescriptorSet DescriptorPool::allocate(const VkDescriptorSetLayout layout) {
std::pair<Result, DescriptorSet> out = allocateInternal(layout);
CORRADE_ASSERT(out.first == Result::Success,
"Vk::DescriptorPool::allocate(): allocation failed with" << out.first, std::move(out.second));
return std::move(out.second);
}
Containers::Optional<DescriptorSet> DescriptorPool::tryAllocate(const VkDescriptorSetLayout layout) {
std::pair<Result, DescriptorSet> out = allocateInternal(layout);
if(out.first != Result::Success) return {};
return std::move(out.second);
}
std::pair<Result, DescriptorSet> DescriptorPool::allocateInternal(const VkDescriptorSetLayout layout, const UnsignedInt variableDescriptorCount) {
DescriptorSet set{NoCreate};
set._device = _device;
set._pool = _handle;
set._flags = _freeAllocatedSets ? HandleFlag::DestroyOnDestruction : HandleFlags{};
VkDescriptorSetVariableDescriptorCountAllocateInfo variableInfo{};
variableInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO;
variableInfo.descriptorSetCount = 1;
variableInfo.pDescriptorCounts = &variableDescriptorCount;
VkDescriptorSetAllocateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
info.pNext = &variableInfo;
info.descriptorPool = _handle;
info.descriptorSetCount = 1;
info.pSetLayouts = &layout;
/* See the not about VK_ERROR_OUT_OF_POOL_MEMORY and VK_KHR_maintenance1
in the other allocateInternal() implementation above. */
const Result result = MAGNUM_VK_INTERNAL_ASSERT_SUCCESS_OR((**_device).AllocateDescriptorSets(*_device, &info, &set._handle), Result::ErrorOutOfPoolMemory, Result::ErrorFragmentedPool);
return {result, std::move(set)};
}
DescriptorSet DescriptorPool::allocate(const VkDescriptorSetLayout layout, const UnsignedInt variableDescriptorCount) {
std::pair<Result, DescriptorSet> out = allocateInternal(layout, variableDescriptorCount);
CORRADE_ASSERT(out.first == Result::Success,
"Vk::DescriptorPool::allocate(): allocation failed with" << out.first, std::move(out.second));
return std::move(out.second);
}
Containers::Optional<DescriptorSet> DescriptorPool::tryAllocate(const VkDescriptorSetLayout layout, const UnsignedInt variableDescriptorCount) {
std::pair<Result, DescriptorSet> out = allocateInternal(layout, variableDescriptorCount);
if(out.first != Result::Success) return {};
return std::move(out.second);
}
void DescriptorPool::reset() {
MAGNUM_VK_INTERNAL_ASSERT_SUCCESS((**_device).ResetDescriptorPool(*_device, _handle, 0));
}
VkDescriptorPool DescriptorPool::release() {
const VkDescriptorPool handle = _handle;
_handle = {};

120
src/Magnum/Vk/DescriptorPool.h

@ -30,6 +30,8 @@
* @m_since_latest
*/
#include <utility>
#include "Magnum/Tags.h"
#include "Magnum/Vk/Handle.h"
#include "Magnum/Vk/visibility.h"
@ -54,6 +56,9 @@ following snippet creates a pool allowing to allocate at most 8 descriptor sets
with 24 sampler bindings and 16 uniform bindings:
@snippet MagnumVk.cpp DescriptorPool-creation
With a descriptor pool created, you can allocate descriptor sets from it. See
the @ref DescriptorSet class for details.
*/
class MAGNUM_VK_EXPORT DescriptorPool {
public:
@ -68,6 +73,17 @@ class MAGNUM_VK_EXPORT DescriptorPool {
* a descriptor pool created using a constructor, the Vulkan descriptor
* pool is by default not deleted on destruction, use @p flags for
* different behavior.
*
* @m_class{m-note m-warning}
*
* @par
* Note that descriptor sets allocated using a pool wrapped by
* this function have no way to know if the pool was created with
* @ref DescriptorPoolCreateInfo::Flag::FreeDescriptorSet and thus
* won't be freeing themselves on destruction. If you need such
* behavior on these, re-wrap the allocated sets with appropriate
* @ref HandleFlags again using @ref DescriptorSet::wrap().
*
* @see @ref release()
*/
static DescriptorPool wrap(Device& device, VkDescriptorPool handle, HandleFlags flags = {});
@ -120,6 +136,106 @@ class MAGNUM_VK_EXPORT DescriptorPool {
/** @brief Handle flags */
HandleFlags handleFlags() const { return _flags; }
/**
* @brief Allocate a single descriptor set
*
* If @p layout contains a binding with
* @ref DescriptorSetLayoutBinding::Flag::VariableDescriptorCount set,
* the allocated descriptor count will be @cpp 0 @ce. Use
* @ref allocate(VkDescriptorSetLayout, UnsignedInt) in that case
* instead.
*
* If allocation fails due to exhaustion of pool memory or due to
* fragmentation, the function aborts with an error message. For
* graceful handling of such failures use
* @ref tryAllocate(VkDescriptorSetLayout) instead.
* @see @ref reset(), @fn_vk_keyword{AllocateDescriptorSets}
*/
DescriptorSet allocate(VkDescriptorSetLayout layout);
/**
* @brief Try to allocate a single descriptor set
*
* Compared to @ref allocate(VkDescriptorSetLayout), if the allocation
* fails with @ref Result::ErrorOutOfPoolMemory or
* @ref Result::ErrorFragmentedPool, @ref Containers::NullOpt is
* returned instead of aborting to allow the application to recover and
* choose a different strategy.
*
* If Vulkan 1.1 is not supported by the device and the
* @vk_extension{KHR,maintenance1} extension isn't enabled on the
* device, allocation failures are treated as user error the driver is
* free to do basically anything. Fortunately most implementations
* support this extension nowadays and so it should be safe to assume
* @ref Result::ErrorOutOfPoolMemory gets properly returned in case of
* a failure.
*/
Containers::Optional<DescriptorSet> tryAllocate(VkDescriptorSetLayout layout);
/**
* @brief Allocate a single descriptor set with a variable descriptor count
*
* Compared to @ref allocate(VkDescriptorSetLayout), the
* @p variableDescriptorCount is used for a binding that was created
* with @ref DescriptorSetLayoutBinding::Flag::VariableDescriptorCount
* and is expected to not be larger than the the count specified in the
* layout.
*
* If allocation fails due to exhaustion of pool memory or due to
* fragmentation, the function aborts with an error message. For
* graceful handling of such failures use
* @ref tryAllocate(VkDescriptorSetLayout, UnsignedInt) instead.
* @see @type_vk_keyword{DescriptorSetVariableDescriptorCountAllocateInfo}
* @requires_vk_feature @ref DeviceFeature::DescriptorBindingVariableDescriptorCount
*/
DescriptorSet allocate(VkDescriptorSetLayout layout, UnsignedInt variableDescriptorCount);
/**
* @brief Try to allocate a single descriptor set with a variable descriptor count
*
* Compared to @ref allocate(VkDescriptorSetLayout, UnsignedInt), if
* the allocation fails with @ref Result::ErrorOutOfPoolMemory or
* @ref Result::ErrorFragmentedPool, @ref Containers::NullOpt is
* returned instead of aborting to allow the application to recover and
* choose a different strategy.
*
* If Vulkan 1.1 is not supported by the device and the
* @vk_extension{KHR,maintenance1} extension isn't enabled on the
* device, allocation failures are treated as user error the driver is
* free to do basically anything. Fortunately most implementations
* support this extension nowadays and so it should be safe to assume
* @ref Result::ErrorOutOfPoolMemory gets properly returned in case of
* a failure.
*/
Containers::Optional<DescriptorSet> tryAllocate(VkDescriptorSetLayout layout, UnsignedInt variableDescriptorCount);
/**
* @brief Reset the pool
*
* Frees all descriptor sets allocated from this pool, making it empty
* again.
*
* @m_class{m-block m-warning}
*
* @par DescriptorSet destruction order
* All @ref DescriptorSet instances returned from @ref allocate()
* / @ref tryAllocate() become invalid after calling this
* function. While by default the @ref DescriptorSet does nothing
* and such behavior is fine, for a pool with
* @ref DescriptorPoolCreateInfo::Flag::FreeDescriptorSet
* enabled this would mean @fn_vk{FreeDescriptorSets} gets called
* with invalid descriptor set handles. To prevent that from
* happening either ensure all @ref DescriptorSet instances are
* gone by the time you call @ref reset(), or explicitly call
* @ref DescriptorSet::release() on each to make them empty
* without freeing anything.
*
* @see @ref allocate(),
* @ref DescriptorPoolCreateInfo::Flag::FreeDescriptorSet,
* @fn_vk{ResetDescriptorPool}
*/
void reset();
/**
* @brief Release the underlying Vulkan descriptor pool
*
@ -132,11 +248,15 @@ class MAGNUM_VK_EXPORT DescriptorPool {
VkDescriptorPool release();
private:
MAGNUM_VK_LOCAL std::pair<Result, DescriptorSet> allocateInternal(VkDescriptorSetLayout layout);
MAGNUM_VK_LOCAL std::pair<Result, DescriptorSet> allocateInternal(VkDescriptorSetLayout layout, UnsignedInt variableDescriptorCount);
/* Can't be a reference because of the NoCreate constructor */
Device* _device;
VkDescriptorPool _handle;
HandleFlags _flags;
bool _freeAllocatedSets;
};
}}

9
src/Magnum/Vk/DescriptorPoolCreateInfo.h

@ -70,8 +70,13 @@ class MAGNUM_VK_EXPORT DescriptorPoolCreateInfo {
*
* @par
* Not using this flag may help the driver to use simpler
* per-pool allocators instead of per-set. Without this flag
* set, descriptor pool fragmentation can't occur.
* per-pool allocators instead of per-set. With this flag set,
* descriptor pool fragmentation may occur, which can result
* in @ref DescriptorPool::allocate() failures even if the
* number of individual free descriptors in the pool is large
* enough.
*
* @see @ref Result::ErrorFragmentedPool
*/
FreeDescriptorSet = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,

69
src/Magnum/Vk/DescriptorSet.cpp

@ -0,0 +1,69 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "DescriptorSet.h"
#include "Magnum/Vk/Assert.h"
#include "Magnum/Vk/Device.h"
#include "Magnum/Vk/Result.h"
namespace Magnum { namespace Vk {
DescriptorSet DescriptorSet::wrap(Device& device, const VkDescriptorPool pool, const VkDescriptorSet handle, const HandleFlags flags) {
DescriptorSet out{NoCreate};
out._device = &device;
out._pool = pool;
out._handle = handle;
out._flags = flags;
return out;
}
DescriptorSet::DescriptorSet(NoCreateT): _device{}, _pool{}, _handle{} {}
DescriptorSet::DescriptorSet(DescriptorSet&& other) noexcept: _device{other._device}, _pool{other._pool}, _handle{other._handle}, _flags{other._flags} {
other._handle = {};
}
DescriptorSet::~DescriptorSet() {
if(_handle && (_flags & HandleFlag::DestroyOnDestruction))
(**_device).FreeDescriptorSets(*_device, _pool, 1, &_handle);
}
DescriptorSet& DescriptorSet::operator=(DescriptorSet&& other) noexcept {
using std::swap;
swap(other._device, _device);
swap(other._pool, _pool);
swap(other._handle, _handle);
swap(other._flags, _flags);
return *this;
}
VkDescriptorSet DescriptorSet::release() {
const VkDescriptorSet handle = _handle;
_handle = {};
return handle;
}
}}

173
src/Magnum/Vk/DescriptorSet.h

@ -0,0 +1,173 @@
#ifndef Magnum_Vk_DescriptorSet_h
#define Magnum_Vk_DescriptorSet_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
/** @file
* @brief Class @ref Magnum::Vk::DescriptorSet
* @m_since_latest
*/
#include "Magnum/Tags.h"
#include "Magnum/Vk/Handle.h"
#include "Magnum/Vk/Vk.h"
#include "Magnum/Vk/Vulkan.h"
#include "Magnum/Vk/visibility.h"
namespace Magnum { namespace Vk {
/**
@brief Descriptor set
@m_since_latest
Wraps a @type_vk_keyword{DescriptorSet}. A descriptor set is allocated from a
@ref DescriptorPool for a particular @ref DescriptorSetLayout and specifies
what descriptors (such as uniform buffers or samplers) are bound to shaders.
@section Vk-DescriptorSet-allocation Descriptor set allocation
Given a @ref DescriptorSetLayout and a compatible @ref DescriptorPool with
enough free slots, asingle descriptor set for given layout can be allocated
with @ref DescriptorPool::allocate():
@snippet MagnumVk.cpp DescriptorSet-allocation
When allocating more than what the pool has, the @ref DescriptorPool::allocate()
function aborts with an error message. In cases where the application is very
dynamic and cannot predict that a pools is large enough, you can use
@ref DescriptorPool::tryAllocate() instead and handle the failure gracefully
--- for example by recycling unused sets or by allocating from a different
pool:
@snippet MagnumVk.cpp DescriptorSet-allocation-try
@subsection Vk-DescriptorSet-allocation-free Freeing descriptor sets
By default, the @ref DescriptorSet destructor is a no-op and descriptor sets
are all freed together on a call to @ref DescriptorPool::reset(). At that point
all existing @ref DescriptorSet instances become invalid. Alternatively, the
pool can be created with @ref DescriptorPoolCreateInfo::Flag::FreeDescriptorSet,
which then makes a @ref DescriptorSet free itself on destruction, allowing more
descriptor sets to be allocated without resetting the whole pool. Using this
flag however can cause allocation to fail also due to pool fragmentation, not
just when exhausing all available resources:
@snippet MagnumVk.cpp DescriptorSet-allocation-free
@subsection Vk-DescriptorSet-allocation-variable Variable descriptor count allocation
If the descriptor set layout contains a descriptor with variable count (there
has to be at most one and it has to be the last binding), a concrete count is
specified in the call to @ref DescriptorPool::allocate(VkDescriptorSetLayout, UnsignedInt). Here the fragment shader can access up to 8 sampled images and
we're allocating four:
@snippet MagnumVk.cpp DescriptorSet-allocation-variable
*/
class MAGNUM_VK_EXPORT DescriptorSet {
public:
/**
* @brief Wrap existing Vulkan handle
* @param device Vulkan device the descriptor pool is
* created on
* @param pool Vulkan descriptor pool the set is
* allocated from
* @param handle The @type_vk{DescriptorSet} handle
* @param flags Handle flags
*
* The @p handle is expected to be originating from @p device. The
* Vulkan descriptor set is by default not freed on destruction --- if
* the handle comes from a pool with
* @ref DescriptorPoolCreateInfo::Flag::FreeDescriptorSet set and you
* want it to be freed on destruction, pass
* @ref HandleFlag::DestroyOnDestruction to @p flags.
* @see @ref release()
*/
static DescriptorSet wrap(Device& device, VkDescriptorPool pool, VkDescriptorSet handle, HandleFlags flags = {});
/**
* @brief Construct without creating the fence
*
* The constructed instance is equivalent to moved-from state. Useful
* in cases where you will overwrite the instance later anyway. Move
* another object over it to make it useful.
*/
explicit DescriptorSet(NoCreateT);
/** @brief Copying is not allowed */
DescriptorSet(const DescriptorSet&) = delete;
/** @brief Move constructor */
DescriptorSet(DescriptorSet&& other) noexcept;
/**
* @brief Destructor
*
* Frees associated @type_vk{DescriptorSet} handle if it was allocated
* from a pool with @ref DescriptorPoolCreateInfo::Flag::FreeDescriptorSet
* set or if it was created using @ref wrap() with
* @ref HandleFlag::DestroyOnDestruction specified. Otherwise does
* nothing.
* @see @fn_vk_keyword{FreeDescriptorSets}, @ref release()
*/
~DescriptorSet();
/** @brief Copying is not allowed */
DescriptorSet& operator=(const DescriptorSet&) = delete;
/** @brief Move assignment */
DescriptorSet& operator=(DescriptorSet&& other) noexcept;
/** @brief Underlying @type_vk{DescriptorSet} handle */
VkDescriptorSet handle() { return _handle; }
/** @overload */
operator VkDescriptorSet() { return _handle; }
/** @brief Handle flags */
HandleFlags handleFlags() const { return _flags; }
/**
* @brief Release the underlying Vulkan descriptor set
*
* Releases ownership of the Vulkan descriptor set and returns its
* handle so @fn_vk{FreeDescriptorSets} is not called on destruction.
* The internal state is then equivalent to moved-from state.
* @see @ref wrap()
*/
VkDescriptorSet release();
private:
friend DescriptorPool;
/* Can't be a reference because of the NoCreate constructor */
Device* _device;
VkDescriptorPool _pool;
VkDescriptorSet _handle;
HandleFlags _flags;
};
}}
#endif

11
src/Magnum/Vk/DescriptorSetLayout.h

@ -42,9 +42,10 @@ namespace Magnum { namespace Vk {
@brief Descriptor set layout
@m_since_latest
Wraps a @type_vk_keyword{DescriptorSetLayout}. A descriptor set layout lists
descriptors (such as uniform buffers or samplers) used by shaders in a
@ref Pipeline.
Wraps a @type_vk_keyword{DescriptorSetLayout}. A descriptor set layout
specifies what descriptors (such as uniform buffers or samplers) can be used by
shaders in a @ref Pipeline, concrete descriptors are then bound using a
@ref DescriptorSet.
@section Vk-DescriptorSetLayout-creation Descriptor set layout creation
@ -89,8 +90,8 @@ specify additional flags per binding. All of them require a certain
@section Vk-DescriptorSetLayout-usage Descriptor set layout usage
A descriptor set layout is used in a @ref PipelineLayout creation and
subsequently for descriptor set allocation from a @ref DescriptorPool. See the
corresponding class documentation for more information.
subsequently for @ref DescriptorSet allocation from a @ref DescriptorPool. See
the corresponding class documentation for more information.
*/
class MAGNUM_VK_EXPORT DescriptorSetLayout {
public:

7
src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h

@ -117,8 +117,9 @@ class MAGNUM_VK_EXPORT DescriptorSetLayoutBinding {
/**
* This descriptor binding has a variable size that will be
* specified when a descriptor set is allocated using this layout,
* and the @p descriptorCount value is treated as an upper bound.
* specified in @ref DescriptorPool::allocate(VkDescriptorSetLayout, UnsignedInt),
* and the @p descriptorCount value specified in the constructor is
* treated as an upper bound.
*
* Allowed only on the last binding number in the layout, not
* allowed on a @ref DescriptorType::UniformBufferDynamic or
@ -146,7 +147,7 @@ class MAGNUM_VK_EXPORT DescriptorSetLayoutBinding {
* binding. If the shader binding is not an array, use @cpp 1 @ce,
* zero is allowed as well. For a @ref Flag::VariableDescriptorCount
* this is used as an upper bound and a concrete size is specified
* during descriptor set allocation.
* in @ref DescriptorPool::allocate(VkDescriptorSetLayout, UnsignedInt).
* @param stages Shader stages that access the binding.
* Use @cpp ~Vk::ShaderStages{} @ce to specify that all stages
* may access the binding.

7
src/Magnum/Vk/Result.h

@ -139,7 +139,9 @@ enum class Result: Int {
ErrorFormatNotSupported = VK_ERROR_FORMAT_NOT_SUPPORTED,
/**
* A pool allocation has failed due to fragmentation of the pool's memory
* A pool allocation has failed due to fragmentation of the pool's memory.
* @see @ref DescriptorPool::allocate(),
* @ref DescriptorPoolCreateInfo::Flag::FreeDescriptorSet
*/
ErrorFragmentedPool = VK_ERROR_FRAGMENTED_POOL,
@ -152,7 +154,8 @@ enum class Result: Int {
/**
* A pool memory allocation has failed.
* @see @ref Result::ErrorOutOfHostMemory,
* @ref Result::ErrorOutOfDeviceMemory
* @ref Result::ErrorOutOfDeviceMemory,
* @ref DescriptorPool::allocate()
* @requires_vk11 Extension @vk_extension{KHR,maintenance1}
*/
ErrorOutOfPoolMemory = VK_ERROR_OUT_OF_POOL_MEMORY,

6
src/Magnum/Vk/Test/CMakeLists.txt

@ -28,6 +28,7 @@ corrade_add_test(VkBufferTest BufferTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkCommandBufferTest CommandBufferTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkCommandPoolTest CommandPoolTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkDescriptorPoolTest DescriptorPoolTest.cpp LIBRARIES MagnumVkTestLib)
corrade_add_test(VkDescriptorSetTest DescriptorSetTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkDescriptorSetLayoutTest DescriptorSetLayoutTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkDescriptorTypeTest DescriptorTypeTest.cpp LIBRARIES MagnumVk)
corrade_add_test(VkDeviceTest DeviceTest.cpp LIBRARIES MagnumVk)
@ -133,6 +134,7 @@ set_target_properties(
VkCommandBufferTest
VkCommandPoolTest
VkDescriptorPoolTest
VkDescriptorSetTest
VkDescriptorSetLayoutTest
VkDescriptorTypeTest
VkDeviceTest
@ -198,7 +200,8 @@ if(BUILD_VK_TESTS)
corrade_add_test(VkBufferVkTest BufferVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester)
corrade_add_test(VkCommandBufferVkTest CommandBufferVkTest.cpp LIBRARIES MagnumVulkanTester)
corrade_add_test(VkCommandPoolVkTest CommandPoolVkTest.cpp LIBRARIES MagnumVulkanTester)
corrade_add_test(VkDescriptorPoolVkTest DescriptorPoolVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester)
corrade_add_test(VkDescriptorPoolVkTest DescriptorPoolVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester)
corrade_add_test(VkDescriptorSetVkTest DescriptorSetVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester)
corrade_add_test(VkDescriptorSetLayoutVkTest DescriptorSetLayoutVkTest.cpp LIBRARIES MagnumVk MagnumVulkanTester)
corrade_add_test(VkDeviceVkTest DeviceVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester)
corrade_add_test(VkDevicePropertiesVkTest DevicePropertiesVkTest.cpp LIBRARIES MagnumVkTestLib MagnumVulkanTester)
@ -252,6 +255,7 @@ if(BUILD_VK_TESTS)
VkCommandBufferVkTest
VkCommandPoolVkTest
VkDescriptorPoolVkTest
VkDescriptorSetVkTest
VkDescriptorSetLayoutVkTest
VkDeviceVkTest
VkDevicePropertiesVkTest

228
src/Magnum/Vk/Test/DescriptorPoolVkTest.cpp

@ -23,8 +23,19 @@
DEALINGS IN THE SOFTWARE.
*/
#include <sstream>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/DebugStl.h>
#include "Magnum/Vk/DescriptorPoolCreateInfo.h"
#include "Magnum/Vk/DescriptorSet.h"
#include "Magnum/Vk/DescriptorSetLayoutCreateInfo.h"
#include "Magnum/Vk/DescriptorType.h"
#include "Magnum/Vk/DeviceCreateInfo.h"
#include "Magnum/Vk/DeviceFeatures.h"
#include "Magnum/Vk/DeviceProperties.h"
#include "Magnum/Vk/ExtensionProperties.h"
#include "Magnum/Vk/Extensions.h"
#include "Magnum/Vk/Result.h"
#include "Magnum/Vk/VulkanTester.h"
@ -33,19 +44,66 @@ namespace Magnum { namespace Vk { namespace Test { namespace {
struct DescriptorPoolVkTest: VulkanTester {
explicit DescriptorPoolVkTest();
void setupVariableDescriptorCount();
void teardown();
void construct();
void constructMove();
void allocate();
void allocateFreeDescriptorSet();
void allocateFail();
void allocateVariableCount();
void allocateVariableCountFreeDescriptorSet();
void allocateVariableCountFail();
void reset();
void wrap();
Queue _queue{NoCreate};
Device _deviceVariableDescriptorCount{NoCreate};
};
DescriptorPoolVkTest::DescriptorPoolVkTest() {
addTests({&DescriptorPoolVkTest::construct,
&DescriptorPoolVkTest::constructMove,
&DescriptorPoolVkTest::allocate,
&DescriptorPoolVkTest::allocateFreeDescriptorSet,
&DescriptorPoolVkTest::allocateFail});
addTests({&DescriptorPoolVkTest::allocateVariableCount,
&DescriptorPoolVkTest::allocateVariableCountFreeDescriptorSet,
&DescriptorPoolVkTest::allocateVariableCountFail},
&DescriptorPoolVkTest::setupVariableDescriptorCount,
&DescriptorPoolVkTest::teardown);
addTests({&DescriptorPoolVkTest::reset,
&DescriptorPoolVkTest::wrap});
}
void DescriptorPoolVkTest::setupVariableDescriptorCount() {
DeviceProperties properties = pickDevice(instance());
if(!properties.enumerateExtensionProperties().isSupported<Extensions::EXT::descriptor_indexing>() ||
!(properties.features() & DeviceFeature::DescriptorBindingVariableDescriptorCount))
return;
/* Create the device only if not already, to avoid spamming the output */
if(!_deviceVariableDescriptorCount.handle()) _deviceVariableDescriptorCount.create(instance(),
DeviceCreateInfo{std::move(properties)}
.addQueues(QueueFlag::Graphics, {0.0f}, {_queue})
.addEnabledExtensions<Extensions::EXT::descriptor_indexing>()
.setEnabledFeatures(DeviceFeature::DescriptorBindingVariableDescriptorCount)
);
}
void DescriptorPoolVkTest::teardown() {
/* Nothing, the device & queue created by setupVariableDescriptorCount()
is created just once and so shouldn't be destroyed right after */
}
void DescriptorPoolVkTest::construct() {
{
DescriptorPool pool{device(), DescriptorPoolCreateInfo{5, {
@ -81,6 +139,176 @@ void DescriptorPoolVkTest::constructMove() {
CORRADE_VERIFY(std::is_nothrow_move_assignable<DescriptorPool>::value);
}
void DescriptorPoolVkTest::allocate() {
DescriptorSetLayout layout{device(), DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer}}
}};
/* We can allocate two sets at most, each with one uniform buffer */
DescriptorPool pool{device(), DescriptorPoolCreateInfo{2, {
{DescriptorType::UniformBuffer, 2}
}}};
{
Containers::Optional<DescriptorSet> allocated = pool.tryAllocate(layout);
CORRADE_VERIFY(allocated);
CORRADE_VERIFY(allocated->handle());
/* No DestroyOnDestruction, the sets get freed only on descriptor pool
reset */
CORRADE_COMPARE(allocated->handleFlags(), HandleFlags{});
} {
DescriptorSet allocated = pool.allocate(layout);
CORRADE_VERIFY(allocated.handle());
/* No DestroyOnDestruction, the sets get freed only on descriptor pool
reset */
CORRADE_COMPARE(allocated.handleFlags(), HandleFlags{});
}
}
void DescriptorPoolVkTest::allocateFail() {
DescriptorSetLayout layout{device(), DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer, 2}},
}};
DescriptorPool pool{device(), DescriptorPoolCreateInfo{1, {
{DescriptorType::UniformBuffer, 1}
}}};
{
/* tryAllocate() should not assert, and should not print anything */
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!pool.tryAllocate(layout));
CORRADE_COMPARE(out.str(), "");
} {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
/* allocate() should assert with ErrorOutOfPoolMemory */
std::ostringstream out;
Error redirectError{&out};
pool.allocate(layout);
CORRADE_COMPARE(out.str(), "Vk::DescriptorPool::allocate(): allocation failed with Vk::Result::ErrorOutOfPoolMemory\n");
}
}
void DescriptorPoolVkTest::allocateFreeDescriptorSet() {
DescriptorSetLayout layout{device(), DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer}}
}};
DescriptorPool pool{device(), DescriptorPoolCreateInfo{1, {
{DescriptorType::UniformBuffer, 1}
}, DescriptorPoolCreateInfo::Flag::FreeDescriptorSet}};
DescriptorSet allocated = pool.allocate(layout);
CORRADE_VERIFY(allocated.handle());
/* vkFreeDescriptorSets() gets called on destruction */
CORRADE_COMPARE(allocated.handleFlags(), HandleFlag::DestroyOnDestruction);
}
void DescriptorPoolVkTest::allocateVariableCount() {
if(!(_deviceVariableDescriptorCount.enabledFeatures() & DeviceFeature::DescriptorBindingVariableDescriptorCount))
CORRADE_SKIP("DeviceFeature::DescriptorBindingVariableDescriptorCount not supported, can't test.");
DescriptorSetLayout layout{_deviceVariableDescriptorCount, DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer, 8, ~ShaderStages{}, DescriptorSetLayoutBinding::Flag::VariableDescriptorCount}}
}};
/* We can allocate two sets at most and at most 12 uniform buffers */
DescriptorPool pool{_deviceVariableDescriptorCount, DescriptorPoolCreateInfo{2, {
{DescriptorType::UniformBuffer, 12}
}}};
{
Containers::Optional<DescriptorSet> allocated = pool.tryAllocate(layout, 8);
CORRADE_VERIFY(allocated);
CORRADE_VERIFY(allocated->handle());
/* No DestroyOnDestruction, the sets get freed only on descriptor pool
reset */
CORRADE_COMPARE(allocated->handleFlags(), HandleFlags{});
} {
DescriptorSet allocated = pool.allocate(layout, 4);
CORRADE_VERIFY(allocated.handle());
/* No DestroyOnDestruction, the sets get freed only on descriptor pool
reset */
CORRADE_COMPARE(allocated.handleFlags(), HandleFlags{});
}
}
void DescriptorPoolVkTest::allocateVariableCountFail() {
if(!(_deviceVariableDescriptorCount.enabledFeatures() & DeviceFeature::DescriptorBindingVariableDescriptorCount))
CORRADE_SKIP("DeviceFeature::DescriptorBindingVariableDescriptorCount not supported, can't test.");
DescriptorSetLayout layout{_deviceVariableDescriptorCount, DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer, 8, ~ShaderStages{}, DescriptorSetLayoutBinding::Flag::VariableDescriptorCount}}
}};
/* We can allocate two sets at most and at most 8 uniform buffers */
DescriptorPool pool{_deviceVariableDescriptorCount, DescriptorPoolCreateInfo{2, {
{DescriptorType::UniformBuffer, 7}
}}};
{
/* tryAllocate() should not assert, and should not print anything */
std::ostringstream out;
Error redirectError{&out};
CORRADE_VERIFY(!pool.tryAllocate(layout, 8));
CORRADE_COMPARE(out.str(), "");
} {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
/* allocate() should assert with ErrorOutOfPoolMemory */
std::ostringstream out;
Error redirectError{&out};
pool.allocate(layout, 8);
CORRADE_COMPARE(out.str(), "Vk::DescriptorPool::allocate(): allocation failed with Vk::Result::ErrorOutOfPoolMemory\n");
}
}
void DescriptorPoolVkTest::allocateVariableCountFreeDescriptorSet() {
if(!(_deviceVariableDescriptorCount.enabledFeatures() & DeviceFeature::DescriptorBindingVariableDescriptorCount))
CORRADE_SKIP("DeviceFeature::DescriptorBindingVariableDescriptorCount not supported, can't test.");
DescriptorSetLayout layout{_deviceVariableDescriptorCount, DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer, 4, ~ShaderStages{}, DescriptorSetLayoutBinding::Flag::VariableDescriptorCount}}
}};
DescriptorPool pool{_deviceVariableDescriptorCount, DescriptorPoolCreateInfo{1, {
{DescriptorType::UniformBuffer, 4}
}, DescriptorPoolCreateInfo::Flag::FreeDescriptorSet}};
DescriptorSet allocated = pool.allocate(layout, 4);
CORRADE_VERIFY(allocated.handle());
/* vkFreeDescriptorSets() gets called on destruction */
CORRADE_COMPARE(allocated.handleFlags(), HandleFlag::DestroyOnDestruction);
}
void DescriptorPoolVkTest::reset() {
DescriptorSetLayout layout{device(), DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer}}
}};
/* Just one */
DescriptorPool pool{device(), DescriptorPoolCreateInfo{1, {
{DescriptorType::UniformBuffer, 1}
}}};
/* First allocation will work */
CORRADE_VERIFY(pool.tryAllocate(layout));
/* Second won't */
CORRADE_VERIFY(!pool.tryAllocate(layout));
pool.reset();
/* Now it will again */
CORRADE_VERIFY(pool.tryAllocate(layout));
}
void DescriptorPoolVkTest::wrap() {
VkDescriptorPool pool{};
CORRADE_COMPARE(Result(device()->CreateDescriptorPool(device(),

62
src/Magnum/Vk/Test/DescriptorSetTest.cpp

@ -0,0 +1,62 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <new>
#include <Corrade/TestSuite/Tester.h>
#include "Magnum/Vk/DescriptorSet.h"
namespace Magnum { namespace Vk { namespace Test { namespace {
struct DescriptorSetTest: TestSuite::Tester {
explicit DescriptorSetTest();
void constructNoCreate();
void constructCopy();
};
DescriptorSetTest::DescriptorSetTest() {
addTests({&DescriptorSetTest::constructNoCreate,
&DescriptorSetTest::constructCopy});
}
void DescriptorSetTest::constructNoCreate() {
{
DescriptorSet set{NoCreate};
CORRADE_VERIFY(!set.handle());
}
/* Implicit construction is not allowed */
CORRADE_VERIFY(!std::is_convertible<NoCreateT, DescriptorSet>::value);
}
void DescriptorSetTest::constructCopy() {
CORRADE_VERIFY(!std::is_copy_constructible<DescriptorSet>{});
CORRADE_VERIFY(!std::is_copy_assignable<DescriptorSet>{});
}
}}}}
CORRADE_TEST_MAIN(Magnum::Vk::Test::DescriptorSetTest)

110
src/Magnum/Vk/Test/DescriptorSetVkTest.cpp

@ -0,0 +1,110 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "Magnum/Vk/DescriptorPoolCreateInfo.h"
#include "Magnum/Vk/DescriptorSet.h"
#include "Magnum/Vk/DescriptorSetLayoutCreateInfo.h"
#include "Magnum/Vk/DescriptorType.h"
#include "Magnum/Vk/Result.h"
#include "Magnum/Vk/VulkanTester.h"
namespace Magnum { namespace Vk { namespace Test { namespace {
struct DescriptorSetVkTest: VulkanTester {
explicit DescriptorSetVkTest();
void constructMove();
void wrap();
};
DescriptorSetVkTest::DescriptorSetVkTest() {
addTests({&DescriptorSetVkTest::constructMove,
&DescriptorSetVkTest::wrap});
}
void DescriptorSetVkTest::constructMove() {
DescriptorSetLayout layout{device(), DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer}}
}};
/* Use the FreeDescriptorSet flag so the allocated descriptor set has
DestroyOnDestruction set */
DescriptorPool pool{device(), DescriptorPoolCreateInfo{1, {
{DescriptorType::UniformBuffer, 1}
}, DescriptorPoolCreateInfo::Flag::FreeDescriptorSet}};
DescriptorSet a = pool.allocate(layout);
VkDescriptorSet handle = a.handle();
DescriptorSet b = std::move(a);
CORRADE_VERIFY(!a.handle());
CORRADE_COMPARE(b.handle(), handle);
CORRADE_COMPARE(b.handleFlags(), HandleFlag::DestroyOnDestruction);
DescriptorSet c{NoCreate};
c = std::move(b);
CORRADE_VERIFY(!b.handle());
CORRADE_COMPARE(b.handleFlags(), HandleFlags{});
CORRADE_COMPARE(c.handle(), handle);
CORRADE_COMPARE(c.handleFlags(), HandleFlag::DestroyOnDestruction);
CORRADE_VERIFY(std::is_nothrow_move_constructible<DescriptorSet>::value);
CORRADE_VERIFY(std::is_nothrow_move_assignable<DescriptorSet>::value);
}
void DescriptorSetVkTest::wrap() {
DescriptorSetLayout layout{device(), DescriptorSetLayoutCreateInfo{
{{0, DescriptorType::UniformBuffer}}
}};
/* Use the FreeDescriptorSet flag so we can explicitly free the thing */
DescriptorPool pool{device(), DescriptorPoolCreateInfo{1, {
{DescriptorType::UniformBuffer, 1}
}, DescriptorPoolCreateInfo::Flag::FreeDescriptorSet}};
VkDescriptorSetAllocateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
info.descriptorPool = pool;
info.descriptorSetCount = 1;
const VkDescriptorSetLayout handle = layout;
info.pSetLayouts = &handle; /* ew */
VkDescriptorSet set{};
CORRADE_COMPARE(Result(device()->AllocateDescriptorSets(device(),
&info, &set)), Result::Success);
auto wrapped = DescriptorSet::wrap(device(), pool, set, HandleFlag::DestroyOnDestruction);
CORRADE_COMPARE(wrapped.handle(), set);
/* Release the handle again, destroy by hand */
CORRADE_COMPARE(wrapped.release(), set);
CORRADE_VERIFY(!wrapped.handle());
device()->FreeDescriptorSets(device(), pool, 1, &set);
}
}}}}
CORRADE_TEST_MAIN(Magnum::Vk::Test::DescriptorSetVkTest)

1
src/Magnum/Vk/Vk.h

@ -59,6 +59,7 @@ enum class DependencyFlag: UnsignedInt;
typedef Containers::EnumSet<DependencyFlag> DependencyFlags;
class DescriptorPool;
class DescriptorPoolCreateInfo;
class DescriptorSet;
class DescriptorSetLayout;
class DescriptorSetLayoutCreateInfo;
enum class DescriptorType: Int;

Loading…
Cancel
Save