diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index 8586ce65a..02ce1316f 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -166,7 +166,7 @@ Vulkan function | Matching API --------------------------------------- | ------------ @fn_vk{EnumerateDeviceLayerProperties} @m_class{m-label m-danger} **deprecated in 1.0.13** | not exposed, [spec commit](https://github.com/KhronosGroup/Vulkan-Docs/commit/2656f459333b3a1dc63619a9ebd83490eea22e93) @fn_vk{EnumerateDeviceExtensionProperties} | | -@fn_vk{EnumerateInstanceExtensionProperties} | | +@fn_vk{EnumerateInstanceExtensionProperties} | @ref enumerateInstanceExtensionProperties() @fn_vk{EnumerateInstanceLayerProperties} | @ref enumerateLayerProperties() @fn_vk{EnumerateInstanceVersion} @m_class{m-label m-flat m-success} **1.1** | @ref enumerateInstanceVersion() @fn_vk{EnumeratePhysicalDevices} | | diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index 1debdbfe1..1ab5af633 100644 --- a/src/Magnum/Vk/CMakeLists.txt +++ b/src/Magnum/Vk/CMakeLists.txt @@ -33,12 +33,14 @@ set(MagnumVk_SRCS set(MagnumVk_GracefulAssert_SRCS Enums.cpp + ExtensionProperties.cpp LayerProperties.cpp) set(MagnumVk_HEADERS Device.h Enums.h Extensions.h + ExtensionProperties.h Instance.h Integration.h LayerProperties.h diff --git a/src/Magnum/Vk/ExtensionProperties.cpp b/src/Magnum/Vk/ExtensionProperties.cpp new file mode 100644 index 000000000..7cd760c12 --- /dev/null +++ b/src/Magnum/Vk/ExtensionProperties.cpp @@ -0,0 +1,182 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 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 "ExtensionProperties.h" + +#include +#include +#include +#include + +#include "Magnum/Vk/Extensions.h" +#include "Magnum/Vk/Result.h" +#include "Magnum/Vk/Version.h" + +namespace Magnum { namespace Vk { + +ExtensionProperties::ExtensionProperties(NoCreateT) {} + +ExtensionProperties::ExtensionProperties(ExtensionProperties&&) noexcept = default; + +ExtensionProperties::~ExtensionProperties() = default; + +ExtensionProperties& ExtensionProperties::operator=(ExtensionProperties&&) noexcept = default; + +ExtensionProperties::ExtensionProperties(const Containers::ArrayView layers, VkResult(*const enumerator)(void*, const char*, UnsignedInt*, VkExtensionProperties*), void* const state) { + /* Retrieve total extension count for all layers + the global extensions */ + std::size_t totalCount = 0; + for(std::size_t i = 0; i <= layers.size(); ++i) { + UnsignedInt count; + MAGNUM_VK_INTERNAL_ASSERT_RESULT(enumerator(state, + i == 0 ? nullptr : + Containers::String::nullTerminatedView(layers[i - 1]).data(), + &count, nullptr)); + + totalCount += count; + } + + /* Allocate extra for a list of string views that we'll use to sort & + search the values and a layer index so we can map the extensions back + to which layer they come from */ + _extensions = Containers::Array{ + reinterpret_cast(new char[totalCount*(sizeof(VkExtensionProperties) + sizeof(Containers::StringView) + sizeof(UnsignedInt))]), + totalCount, + [](VkExtensionProperties* data, std::size_t) { + delete[] reinterpret_cast(data); + }}; + Containers::ArrayView extensionNames{reinterpret_cast(_extensions.end()), totalCount}; + Containers::ArrayView extensionLayers{reinterpret_cast(extensionNames.end()), totalCount}; + + /* Query the extensions, save layer ID for each */ + std::size_t offset = 0; + for(std::size_t i = 0; i <= layers.size(); ++i) { + UnsignedInt count = totalCount - offset; + MAGNUM_VK_INTERNAL_ASSERT_RESULT(enumerator(state, + i == 0 ? nullptr : + Containers::String::nullTerminatedView(layers[i - 1]).data(), + &count, reinterpret_cast(_extensions.data()) + offset)); + for(std::size_t j = 0; j != count; ++j) extensionLayers[offset + j] = i; + offset += count; + } + + /* Expect the total extension count didn't change between calls */ + CORRADE_INTERNAL_ASSERT(offset == totalCount); + + /* Populate the views, sort them and remove duplicates so we can search in + O(log n) later */ + for(std::size_t i = 0; i != extensionNames.size(); ++i) + extensionNames[i] = _extensions[i].extensionName; + std::sort(extensionNames.begin(), extensionNames.end()); + _uniqueExtensionCount = std::unique(extensionNames.begin(), extensionNames.end()) - extensionNames.begin(); +} + +Containers::ArrayView ExtensionProperties::names() const { + return {reinterpret_cast(_extensions.end()), _uniqueExtensionCount}; +} + +bool ExtensionProperties::isSupported(const Containers::StringView extension) const { + return std::binary_search( + reinterpret_cast(_extensions.end()), + reinterpret_cast(_extensions.end()) + _uniqueExtensionCount, + extension); +} + +bool ExtensionProperties::isSupported(const Extension& extension) const { + return isSupported(extension.string()); +} + +Containers::StringView ExtensionProperties::name(const UnsignedInt id) const { + CORRADE_ASSERT(id < _extensions.size(), + "Vk::ExtensionProperties::name(): index" << id << "out of range for" << _extensions.size() << "entries", {}); + /* Not returning the string views at the end because those are in a + different order */ + return _extensions[id].extensionName; +} + +UnsignedInt ExtensionProperties::revision(const Extension& extension) const { + return revision(extension.string()); +} + +UnsignedInt ExtensionProperties::revision(const UnsignedInt id) const { + CORRADE_ASSERT(id < _extensions.size(), + "Vk::ExtensionProperties::revision(): index" << id << "out of range for" << _extensions.size() << "entries", {}); + /* WTF, why VkLayerProperties::specVersion is an actual Vulkan version and + here it is a revision number?! Consistency my ass. */ + return _extensions[id].specVersion; +} + +UnsignedInt ExtensionProperties::revision(const Containers::StringView extension) const { + /* Thanks, C++, for forcing me to do one more comparison than strictly + necessary */ + auto found = std::lower_bound( + reinterpret_cast(_extensions.end()), + reinterpret_cast(_extensions.end()) + _uniqueExtensionCount, + extension); + if(*found != extension) return 0; + + /* The view target is contents of the VkExtensionProperties structure, + the revision is stored nearby */ + return reinterpret_cast(found->data() - offsetof(VkExtensionProperties, extensionName))->specVersion; +} + +UnsignedInt ExtensionProperties::layer(const UnsignedInt id) const { + CORRADE_ASSERT(id < _extensions.size(), + "Vk::xtensionProperties::layer(): index" << id << "out of range for" << _extensions.size() << "entries", {}); + return reinterpret_cast(reinterpret_cast(_extensions.end()) + _extensions.size())[id]; +} + +InstanceExtensionProperties::InstanceExtensionProperties(InstanceExtensionProperties&&) noexcept = default; + +InstanceExtensionProperties::~InstanceExtensionProperties() = default; + +InstanceExtensionProperties& InstanceExtensionProperties::operator=(InstanceExtensionProperties&&) noexcept = default; + +bool InstanceExtensionProperties::isSupported(Containers::StringView extension) const { + return ExtensionProperties::isSupported(extension); +} + +bool InstanceExtensionProperties::isSupported(const InstanceExtension& extension) const { + return isSupported(extension.string()); +} + +UnsignedInt InstanceExtensionProperties::revision(Containers::StringView extension) const { + return ExtensionProperties::revision(extension); +} + +UnsignedInt InstanceExtensionProperties::revision(const InstanceExtension& extension) const { + return revision(extension.string()); +} + +InstanceExtensionProperties enumerateInstanceExtensionProperties(const Containers::ArrayView layers) { + return InstanceExtensionProperties{layers, [](void*, const char* const layer, UnsignedInt* count, VkExtensionProperties* properties) { + return vkEnumerateInstanceExtensionProperties(layer, count, properties); + }, nullptr}; +} + +InstanceExtensionProperties enumerateInstanceExtensionProperties(const std::initializer_list layers) { + return enumerateInstanceExtensionProperties(Containers::arrayView(layers)); +} + +}} diff --git a/src/Magnum/Vk/ExtensionProperties.h b/src/Magnum/Vk/ExtensionProperties.h new file mode 100644 index 000000000..4c3bfe9cd --- /dev/null +++ b/src/Magnum/Vk/ExtensionProperties.h @@ -0,0 +1,263 @@ +#ifndef Magnum_Vk_ExtensionProperties_h +#define Magnum_Vk_ExtensionProperties_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 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::ExtensionProperties, @ref Magnum::Vk::InstanceExtensionProperties, function @ref enumerateInstanceExtensionProperties() + * @m_since_latest + */ + +#include + +#include "Magnum/Tags.h" +#include "Magnum/Magnum.h" +#include "Magnum/Vk/TypeTraits.h" +#include "Magnum/Vk/Vk.h" +#include "Magnum/Vk/Vulkan.h" +#include "Magnum/Vk/visibility.h" + +namespace Magnum { namespace Vk { + +/** +@brief Extension properties +@m_since_latest + +@see @ref InstanceExtensionProperties, @type_vk_keyword{ExtensionProperties} +*/ +class MAGNUM_VK_EXPORT ExtensionProperties { + public: + /** + * @brief Construct without populating the contents + * + * Equivalent to a moved-from state. + */ + explicit ExtensionProperties(NoCreateT); + + /** @brief Copying is not allowed */ + ExtensionProperties(const ExtensionProperties&) = delete; + + /** @brief Move constructor */ + ExtensionProperties(ExtensionProperties&&) noexcept; + + ~ExtensionProperties(); + + /** @brief Copying is not allowed */ + ExtensionProperties& operator=(const ExtensionProperties&) = delete; + + /** @brief Move assignment */ + ExtensionProperties& operator=(ExtensionProperties&&) noexcept; + + /** + * @brief Instance extensions + * + * A list of all extension strings reported by the driver for all + * layers passed to the constructor, with duplicates removed. Use + * @ref isSupported() to query support of a particular + * extension. Note that the list is sorted and thus may be different + * than the order in which the @ref name() and @ref revision() + * accessors return values. + * + * The returned views are owned by the + * @ref ExtensionProperties instance (i.e., *not* a global + * memory). + */ + Containers::ArrayView names() const; + + /** + * @brief Whether given extension is supported + * + * Accepts extensions from the @ref Extension namespace as a template + * parameter. Use the other overloads to query support of a runtime + * extension or a plain extension string. + * + * Search complexity is @f$ \mathcal{O}(\log n) @f$ in the total + * extension count; in contrast extension queries on a created instance + * are @f$ \mathcal{O}(1) @f$. + * @see @ref revision() + */ + bool isSupported(Containers::StringView extension) const; + + /** @overload */ + bool isSupported(const Extension& extension) const; + + /** @overload */ + template bool isSupported() const { + static_assert(Implementation::IsExtension::value, "expected a Vulkan deviceension"); + return isSupported(E::string()); + } + + /** + * @brief Count of extensions reported by the driver for all layers + * + * The count includes potential duplicates when an extension is both + * available globally and through a particular layer. + */ + UnsignedInt count() const { return _extensions.size(); } + + /** + * @brief Extension name + * @param id Extension index, expected to be smaller than @ref count() + * + * The returned view is owned by the + * @ref ExtensionProperties instance (i.e., *not* a global + * memory). + */ + Containers::StringView name(UnsignedInt id) const; + + /** + * @brief Extension revision + * @param id Extension index, expected to be smaller than @ref count() + */ + UnsignedInt revision(UnsignedInt id) const; + + /** + * @brief Revision of a particular extension name + * + * If the extension is not supported, returns @cpp 0 @ce, supported + * extensions always have a non-zero revision. If the extension is + * implemented by more than one layer, returns revision of the first + * layer implementing it --- use @ref revision(UnsignedInt) const + * to get revision of a concrete extension in a concrete layer. + * @see @ref isSupported() + */ + UnsignedInt revision(Containers::StringView extension) const; + + /** @overload */ + UnsignedInt revision(const Extension& extension) const; + + /** @overload */ + template UnsignedInt revision() const { + static_assert(Implementation::IsExtension::value, "expected a Vulkan device extension"); + return revision(E::string()); + } + + /** + * @brief Extension layer index + * @param id Extension index, expected to be smaller than @ref count() + * + * Returns ID of the layer the extension comes from. @cpp 0 @ce is + * global extensions, @cpp 1 @ce is the first layer passed to + * @ref enumerateInstanceExtensionProperties() and so on. + */ + UnsignedInt layer(UnsignedInt id) const; + + private: + friend class InstanceExtensionProperties; + + explicit ExtensionProperties(const Containers::ArrayView layers, VkResult(*enumerator)(void*, const char*, UnsignedInt*, VkExtensionProperties*), void* state); + + Containers::Array _extensions; + std::size_t _uniqueExtensionCount; +}; + +/** +@brief Instance extension properties +@m_since_latest + +Provides a searchable container of Vulkan device extensions enumerated with +@ref enumerateInstanceExtensionProperties(). +@see @ref ExtensionProperties, @ref LayerProperties +*/ +class MAGNUM_VK_EXPORT InstanceExtensionProperties: public ExtensionProperties { + public: + /** + * @brief Construct without populating the contents + * + * Equivalent to a moved-from state. Move over the result of + * @ref enumerateInstanceExtensionProperties() to make it usable. + */ + explicit InstanceExtensionProperties(NoCreateT): ExtensionProperties{NoCreate} {} + + /** @brief Copying is not allowed */ + InstanceExtensionProperties(const InstanceExtensionProperties&) = delete; + + /** @brief Move constructor */ + InstanceExtensionProperties(InstanceExtensionProperties&&) noexcept; + + ~InstanceExtensionProperties(); + + /** @brief Copying is not allowed */ + InstanceExtensionProperties& operator=(const InstanceExtensionProperties&) = delete; + + /** @brief Move assignment */ + InstanceExtensionProperties& operator=(InstanceExtensionProperties&&) noexcept; + + /** @copydoc ExtensionProperties::isSupported(Containers::StringView) const */ + bool isSupported(Containers::StringView extension) const; + + /** @overload */ + bool isSupported(const InstanceExtension& extension) const; + + /** @overload */ + template bool isSupported() const { + static_assert(Implementation::IsInstanceExtension::value, "expected a Vulkan instance extension"); + return isSupported(E::string()); + } + + /** @copydoc ExtensionProperties::revision(UnsignedInt) const */ + UnsignedInt revision(UnsignedInt id) const { + return ExtensionProperties::revision(id); + } + + /** @copydoc ExtensionProperties::revision(Containers::StringView) const */ + UnsignedInt revision(Containers::StringView extension) const; + + /** @overload */ + UnsignedInt revision(const InstanceExtension& extension) const; + + /** @overload */ + template UnsignedInt revision() const { + static_assert(Implementation::IsInstanceExtension::value, "expected a Vulkan instance extension"); + return revision(E::string()); + } + + private: + #ifndef DOXYGEN_GENERATING_OUTPUT + /* The DAMN THING forgets parameter name if this is present, FFS. It + also lists this among friends, which is AN IMPLEMENTATION DETAIL */ + friend MAGNUM_VK_EXPORT InstanceExtensionProperties enumerateInstanceExtensionProperties(Containers::ArrayView); + #endif + + explicit InstanceExtensionProperties(const Containers::ArrayView layers, VkResult(*enumerator)(void*, const char*, UnsignedInt*, VkExtensionProperties*), void* state): ExtensionProperties{layers, enumerator, state} {} +}; + +/** +@brief Enumerate instance extension properties +@param layers Additional layers to list extensions from +@m_since_latest + +Expects that all listed layers are supported. +@see @ref LayerProperties::isSupported(), + @fn_vk_keyword{EnumerateExtensionProperties} +*/ +MAGNUM_VK_EXPORT InstanceExtensionProperties enumerateInstanceExtensionProperties(Containers::ArrayView layers = {}); + +/** @overload */ +MAGNUM_VK_EXPORT InstanceExtensionProperties enumerateInstanceExtensionProperties(std::initializer_list layers); + +}} + +#endif diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index 22b99e606..bc3891281 100644 --- a/src/Magnum/Vk/Test/CMakeLists.txt +++ b/src/Magnum/Vk/Test/CMakeLists.txt @@ -26,12 +26,14 @@ corrade_add_test(VkEnumsTest EnumsTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkExtensionsTest ExtensionsTest.cpp LIBRARIES MagnumVk) +corrade_add_test(VkExtensionPropertiesTest ExtensionPropertiesTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkIntegrationTest IntegrationTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkLayerPropertiesTest LayerPropertiesTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkResultTest ResultTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkVersionTest VersionTest.cpp LIBRARIES MagnumVk) if(BUILD_VK_TESTS) + corrade_add_test(VkExtensionPropertiesVkTest ExtensionPropertiesVkTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkLayerPropertiesVkTest LayerPropertiesVkTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkVersionVkTest VersionVkTest.cpp LIBRARIES MagnumVk) endif() diff --git a/src/Magnum/Vk/Test/ExtensionPropertiesTest.cpp b/src/Magnum/Vk/Test/ExtensionPropertiesTest.cpp new file mode 100644 index 000000000..b997d8f30 --- /dev/null +++ b/src/Magnum/Vk/Test/ExtensionPropertiesTest.cpp @@ -0,0 +1,82 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 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 "Magnum/Vk/ExtensionProperties.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct ExtensionPropertiesTest: TestSuite::Tester { + explicit ExtensionPropertiesTest(); + + void constructNoCreate(); + void constructInstanceNoCreate(); + + void constructCopy(); + void constructInstanceCopy(); +}; + +ExtensionPropertiesTest::ExtensionPropertiesTest() { + addTests({&ExtensionPropertiesTest::constructNoCreate, + &ExtensionPropertiesTest::constructInstanceNoCreate, + + &ExtensionPropertiesTest::constructCopy, + &ExtensionPropertiesTest::constructInstanceCopy}); +} + +void ExtensionPropertiesTest::constructNoCreate() { + { + ExtensionProperties properties{NoCreate}; + CORRADE_COMPARE(properties.count(), 0); + } + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void ExtensionPropertiesTest::constructInstanceNoCreate() { + { + InstanceExtensionProperties properties{NoCreate}; + CORRADE_COMPARE(properties.count(), 0); + } + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void ExtensionPropertiesTest::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +void ExtensionPropertiesTest::constructInstanceCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::ExtensionPropertiesTest) diff --git a/src/Magnum/Vk/Test/ExtensionPropertiesVkTest.cpp b/src/Magnum/Vk/Test/ExtensionPropertiesVkTest.cpp new file mode 100644 index 000000000..513191e97 --- /dev/null +++ b/src/Magnum/Vk/Test/ExtensionPropertiesVkTest.cpp @@ -0,0 +1,221 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 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 +#include +#include +#include + +#include "Magnum/Vk/Extensions.h" +#include "Magnum/Vk/LayerProperties.h" +#include "Magnum/Vk/ExtensionProperties.h" +#include "Magnum/Vk/Version.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct ExtensionPropertiesVkTest: TestSuite::Tester { + explicit ExtensionPropertiesVkTest(); + + void constructMove(); + /* Device extension move tested in DevicePropertiesVkTest */ + + void enumerateInstance(); + void enumerateInstanceWithKhronosValidationLayer(); + void enumerateInstanceNonexistentLayer(); + void instanceExtensionIsSupported(); + + /* Device extensions tested in DevicePropertiesVkTest */ + void outOfRange(); + void namedRevision(); +}; + +ExtensionPropertiesVkTest::ExtensionPropertiesVkTest() { + addTests({&ExtensionPropertiesVkTest::constructMove, + + &ExtensionPropertiesVkTest::enumerateInstance, + &ExtensionPropertiesVkTest::enumerateInstanceWithKhronosValidationLayer, + &ExtensionPropertiesVkTest::enumerateInstanceNonexistentLayer, + &ExtensionPropertiesVkTest::instanceExtensionIsSupported, + + &ExtensionPropertiesVkTest::outOfRange, + &ExtensionPropertiesVkTest::namedRevision}); +} + +using namespace Containers::Literals; + +void ExtensionPropertiesVkTest::constructMove() { + InstanceExtensionProperties a = enumerateInstanceExtensionProperties(); + const UnsignedInt count = a.count(); + if(!count) CORRADE_SKIP("No extensions reported, can't test"); + + InstanceExtensionProperties b = std::move(a); + CORRADE_COMPARE(b.count(), count); + + InstanceExtensionProperties c{NoCreate}; + c = std::move(b); + CORRADE_COMPARE(c.count(), count); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void ExtensionPropertiesVkTest::enumerateInstance() { + InstanceExtensionProperties properties = enumerateInstanceExtensionProperties(); + Debug{} << "Available instance extension count:" << properties.names().size(); + + CORRADE_COMPARE_AS(properties.count(), 0, TestSuite::Compare::Greater); + for(std::size_t i = 0; i != properties.count(); ++i) { + using namespace Containers::Literals; + CORRADE_ITERATION(properties.name(i)); + CORRADE_COMPARE_AS(properties.name(i).size(), "VK_"_s.size(), + TestSuite::Compare::Greater); + CORRADE_COMPARE_AS(properties.revision(i), 0, + TestSuite::Compare::Greater); + /* All extensions are from the global layer */ + CORRADE_COMPARE(properties.layer(i), 0); + } + + /* The extension list should be sorted and unique (so Less, not + LessOrEqual) */ + Containers::ArrayView extensions = properties.names(); + for(std::size_t i = 1; i != extensions.size(); ++i) { + CORRADE_COMPARE_AS(extensions[i - 1], extensions[i], + TestSuite::Compare::Less); + } +} + +void ExtensionPropertiesVkTest::enumerateInstanceWithKhronosValidationLayer() { + if(!enumerateLayerProperties().isSupported("VK_LAYER_KHRONOS_validation")) + CORRADE_SKIP("VK_LAYER_KHRONOS_validation not supported, can't test"); + + /* There should be more extensions with this layer enabled */ + InstanceExtensionProperties global = enumerateInstanceExtensionProperties(); + InstanceExtensionProperties withKhronosValidation = enumerateInstanceExtensionProperties({"VK_LAYER_KHRONOS_validation"}); + CORRADE_COMPARE_AS(global.count(), + withKhronosValidation.count(), + TestSuite::Compare::Less); + + /* The extension list should be sorted even including the extra layers, and + unique (so Less, not LessOrEqual) */ + Containers::ArrayView extensions = withKhronosValidation.names(); + for(std::size_t i = 1; i != extensions.size(); ++i) { + CORRADE_COMPARE_AS(extensions[i - 1], extensions[i], + TestSuite::Compare::Less); + } + + /* The VK_LAYER_KHRONOS_validation adds extensions that are supported + globally, which means extensionCount() should be larger than + extensions.size() as it has some duplicates */ + CORRADE_COMPARE_AS(withKhronosValidation.count(), extensions.size(), + TestSuite::Compare::Greater); + + /* The last extension should be from the validation layer */ + CORRADE_COMPARE(withKhronosValidation.layer(0), 0); + CORRADE_COMPARE(withKhronosValidation.layer(withKhronosValidation.count() - 1), 1); + + /* VK_EXT_validation_features is only in the layer */ + CORRADE_VERIFY(!global.isSupported("VK_EXT_validation_features")); + CORRADE_VERIFY(withKhronosValidation.isSupported("VK_EXT_validation_features")); +} + +void ExtensionPropertiesVkTest::enumerateInstanceNonexistentLayer() { + CORRADE_SKIP("Currently this hits an internal assert, which can't be tested."); + + std::ostringstream out; + Error redirectError{&out}; + enumerateInstanceExtensionProperties({"VK_LAYER_this_doesnt_exist"}); + CORRADE_COMPARE(out.str(), "TODO"); +} + +void ExtensionPropertiesVkTest::instanceExtensionIsSupported() { + InstanceExtensionProperties properties = enumerateInstanceExtensionProperties(); + CORRADE_COMPARE_AS(properties.count(), 0, TestSuite::Compare::Greater); + + for(UnsignedInt i = 0; i != properties.count(); ++i) { + CORRADE_ITERATION(properties.name(i)); + CORRADE_VERIFY(properties.isSupported(properties.name(i))); + } + + CORRADE_VERIFY(!properties.isSupported("VK_this_doesnt_exist")); + + /* Verify that we're not just comparing a prefix */ + const std::string extension = std::string(properties.name(0)) + "_hello"; + CORRADE_VERIFY(!properties.isSupported(extension)); + + /* This extension should be available almost always */ + if(!properties.isSupported("VK_KHR_get_physical_device_properties2")) + CORRADE_SKIP("VK_KHR_get_physical_device_properties2 not supported, can't fully test"); + + /* Verify the overloads that take our extension wrappers work as well */ + CORRADE_VERIFY(properties.isSupported()); + CORRADE_VERIFY(properties.isSupported(Extensions::KHR::get_physical_device_properties2{})); +} + +void ExtensionPropertiesVkTest::outOfRange() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + InstanceExtensionProperties properties = enumerateInstanceExtensionProperties(); + const UnsignedInt count = properties.count(); + + std::ostringstream out; + Error redirectError{&out}; + properties.name(count); + properties.revision(count); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Vk::ExtensionProperties::name(): index {0} out of range for {0} entries\n" + "Vk::ExtensionProperties::revision(): index {0} out of range for {0} entries\n", count)); +} + +void ExtensionPropertiesVkTest::namedRevision() { + InstanceExtensionProperties properties = enumerateInstanceExtensionProperties(); + /** @todo use Extensions::KHR::surface once the extension is recognized */ + if(!properties.isSupported("VK_KHR_surface")) + CORRADE_SKIP("VK_KHR_surface not supported, can't test"); + if(!properties.isSupported()) + CORRADE_SKIP("VK_KHR_get_physical_device_properties2 not supported, can't test"); + + /* It was at revision 25 in January 2016, which is four months before + Vulkan first came out, so it's safe to assume all drivers have this + revision by now */ + CORRADE_COMPARE_AS(properties.revision("VK_KHR_surface"), 25, + TestSuite::Compare::GreaterOrEqual); + + /* Unknown extensions return 0 */ + CORRADE_COMPARE(properties.revision("VK_this_doesnt_exist"), 0); + + /* Verify the overloads that take our extension wrappers work as well */ + CORRADE_COMPARE_AS(properties.revision(), 0, + TestSuite::Compare::Greater); + CORRADE_COMPARE_AS(properties.revision(Extensions::KHR::get_physical_device_properties2{}), 0, + TestSuite::Compare::Greater); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::ExtensionPropertiesVkTest) diff --git a/src/Magnum/Vk/Vk.h b/src/Magnum/Vk/Vk.h index 253176489..7ebd6d838 100644 --- a/src/Magnum/Vk/Vk.h +++ b/src/Magnum/Vk/Vk.h @@ -35,7 +35,9 @@ namespace Magnum { namespace Vk { #ifndef DOXYGEN_GENERATING_OUTPUT class Extension; +class ExtensionProperties; class InstanceExtension; +class InstanceExtensionProperties; class LayerProperties; enum class Result: Int;