diff --git a/doc/snippets/MagnumVk.cpp b/doc/snippets/MagnumVk.cpp index fb08bb3b5..8978ae08d 100644 --- a/doc/snippets/MagnumVk.cpp +++ b/doc/snippets/MagnumVk.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -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 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() + .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 */ diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index 358c22d0b..617d805c0 100644 --- a/doc/vulkan-mapping.dox +++ b/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** | | diff --git a/doc/vulkan-support.dox b/doc/vulkan-support.dox index ce90aeae7..2666bb064 100644 --- a/doc/vulkan-support.dox +++ b/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} | | diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index f4d189ccb..a12bbca0d 100644 --- a/src/Magnum/Vk/CMakeLists.txt +++ b/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 diff --git a/src/Magnum/Vk/DescriptorPool.cpp b/src/Magnum/Vk/DescriptorPool.cpp index d9a1616c1..2feeb1f75 100644 --- a/src/Magnum/Vk/DescriptorPool.cpp +++ b/src/Magnum/Vk/DescriptorPool.cpp @@ -27,8 +27,10 @@ #include "DescriptorPoolCreateInfo.h" #include +#include #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 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 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 DescriptorPool::tryAllocate(const VkDescriptorSetLayout layout) { + std::pair out = allocateInternal(layout); + if(out.first != Result::Success) return {}; + return std::move(out.second); +} + +std::pair 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 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 DescriptorPool::tryAllocate(const VkDescriptorSetLayout layout, const UnsignedInt variableDescriptorCount) { + std::pair 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 = {}; diff --git a/src/Magnum/Vk/DescriptorPool.h b/src/Magnum/Vk/DescriptorPool.h index 7976b435e..c610ea8d4 100644 --- a/src/Magnum/Vk/DescriptorPool.h +++ b/src/Magnum/Vk/DescriptorPool.h @@ -30,6 +30,8 @@ * @m_since_latest */ +#include + #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 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 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 allocateInternal(VkDescriptorSetLayout layout); + MAGNUM_VK_LOCAL std::pair allocateInternal(VkDescriptorSetLayout layout, UnsignedInt variableDescriptorCount); + /* Can't be a reference because of the NoCreate constructor */ Device* _device; VkDescriptorPool _handle; HandleFlags _flags; + bool _freeAllocatedSets; }; }} diff --git a/src/Magnum/Vk/DescriptorPoolCreateInfo.h b/src/Magnum/Vk/DescriptorPoolCreateInfo.h index 3d84c3934..c7dd0ca94 100644 --- a/src/Magnum/Vk/DescriptorPoolCreateInfo.h +++ b/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, diff --git a/src/Magnum/Vk/DescriptorSet.cpp b/src/Magnum/Vk/DescriptorSet.cpp new file mode 100644 index 000000000..1af1c46ae --- /dev/null +++ b/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š + + 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; +} + +}} diff --git a/src/Magnum/Vk/DescriptorSet.h b/src/Magnum/Vk/DescriptorSet.h new file mode 100644 index 000000000..f684510b7 --- /dev/null +++ b/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š + + 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 diff --git a/src/Magnum/Vk/DescriptorSetLayout.h b/src/Magnum/Vk/DescriptorSetLayout.h index d537e6e5a..ced48ee43 100644 --- a/src/Magnum/Vk/DescriptorSetLayout.h +++ b/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: diff --git a/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h b/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h index b072ca494..8e6f5d998 100644 --- a/src/Magnum/Vk/DescriptorSetLayoutCreateInfo.h +++ b/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. diff --git a/src/Magnum/Vk/Result.h b/src/Magnum/Vk/Result.h index 66a012131..88a2e8bc9 100644 --- a/src/Magnum/Vk/Result.h +++ b/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, diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index 8279445a6..fbe926370 100644 --- a/src/Magnum/Vk/Test/CMakeLists.txt +++ b/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 diff --git a/src/Magnum/Vk/Test/DescriptorPoolVkTest.cpp b/src/Magnum/Vk/Test/DescriptorPoolVkTest.cpp index 66a58ba5b..0906edde6 100644 --- a/src/Magnum/Vk/Test/DescriptorPoolVkTest.cpp +++ b/src/Magnum/Vk/Test/DescriptorPoolVkTest.cpp @@ -23,8 +23,19 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include +#include + #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() || + !(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() + .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::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 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 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(), diff --git a/src/Magnum/Vk/Test/DescriptorSetTest.cpp b/src/Magnum/Vk/Test/DescriptorSetTest.cpp new file mode 100644 index 000000000..0a07e5120 --- /dev/null +++ b/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š + + 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 +#include + +#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::value); +} + +void DescriptorSetTest::constructCopy() { + CORRADE_VERIFY(!std::is_copy_constructible{}); + CORRADE_VERIFY(!std::is_copy_assignable{}); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::DescriptorSetTest) diff --git a/src/Magnum/Vk/Test/DescriptorSetVkTest.cpp b/src/Magnum/Vk/Test/DescriptorSetVkTest.cpp new file mode 100644 index 000000000..11654e3a9 --- /dev/null +++ b/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š + + 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::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::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) diff --git a/src/Magnum/Vk/Vk.h b/src/Magnum/Vk/Vk.h index 723772562..913abbe59 100644 --- a/src/Magnum/Vk/Vk.h +++ b/src/Magnum/Vk/Vk.h @@ -59,6 +59,7 @@ enum class DependencyFlag: UnsignedInt; typedef Containers::EnumSet DependencyFlags; class DescriptorPool; class DescriptorPoolCreateInfo; +class DescriptorSet; class DescriptorSetLayout; class DescriptorSetLayoutCreateInfo; enum class DescriptorType: Int;