diff --git a/doc/developers.dox b/doc/developers.dox index 9dfb31349..6ab8fa9e2 100644 --- a/doc/developers.dox +++ b/doc/developers.dox @@ -868,7 +868,12 @@ pointers on a class that needs them: before. For some reason the Khronos validation layer doesn't check `sType` fields in this query so it's easy to make a hard-to-discover error -9. Add a link to the new Feature structure in `doc/vulkan-mapping.dox` +9. Extend @ref Vk::DeviceCreateInfo::setEnabledFeatures(): + - a similar addition as above, connecting the structure to the chain at + correct place, + - to the correct place in the structureChainDisconnect() call, + - and function documentation again +10. Add a link to the new Feature structure in `doc/vulkan-mapping.dox` @section developers-dependency Checklist for adding, removing or updating a dependency diff --git a/doc/snippets/MagnumVk.cpp b/doc/snippets/MagnumVk.cpp index 2a839ce2a..afa35a132 100644 --- a/doc/snippets/MagnumVk.cpp +++ b/doc/snippets/MagnumVk.cpp @@ -34,6 +34,7 @@ #include "Magnum/Vk/CommandBuffer.h" #include "Magnum/Vk/CommandPoolCreateInfo.h" #include "Magnum/Vk/DeviceCreateInfo.h" +#include "Magnum/Vk/DeviceFeatures.h" #include "Magnum/Vk/DeviceProperties.h" #include "Magnum/Vk/Extensions.h" #include "Magnum/Vk/ExtensionProperties.h" @@ -214,6 +215,22 @@ Vk::Device device{instance, Vk::DeviceCreateInfo{DOXYGEN_IGNORE(properties)} /* [Device-creation-extensions] */ } +{ +Vk::Instance instance; +Vk::DeviceProperties properties{NoCreate}; +using namespace Containers::Literals; +/* [Device-creation-features] */ +Vk::Device device{instance, Vk::DeviceCreateInfo{DOXYGEN_IGNORE(properties)} + DOXYGEN_IGNORE() + .setEnabledFeatures( + Vk::DeviceFeature::IndexTypeUint8| + Vk::DeviceFeature::SamplerAnisotropy| + Vk::DeviceFeature::GeometryShader| + DOXYGEN_IGNORE(Vk::DeviceFeature{})) +}; +/* [Device-creation-features] */ +} + { Vk::Instance instance; using namespace Containers::Literals; @@ -227,6 +244,11 @@ if(extensions.isSupported()) if(extensions.isSupported("VK_NV_mesh_shader"_s)) info.addEnabledExtensions({"VK_NV_mesh_shader"_s}); DOXYGEN_IGNORE() +info.setEnabledFeatures(properties.features() & // mask away unsupported ones + (Vk::DeviceFeature::IndexTypeUint8| + Vk::DeviceFeature::SamplerAnisotropy| + Vk::DeviceFeature::GeometryShader| + DOXYGEN_IGNORE(Vk::DeviceFeature{}))); /* [Device-creation-check-supported] */ } diff --git a/doc/vulkan-wrapping.dox b/doc/vulkan-wrapping.dox index c3bed5cd1..174049256 100644 --- a/doc/vulkan-wrapping.dox +++ b/doc/vulkan-wrapping.dox @@ -133,7 +133,24 @@ care to not clash with values and pointers already set: Similarly to the @ref NoInit constructors, constructing a `Vk::*CreateInfo` from the underlying Vulkan structure is guaranteed to not allocate as well --- only a shallow copy of the top-level structure is made and internal pointers, -if any, keep pointing to the originating data. +if any, keep pointing to the originating data. That approach has some +implications: + +- The user is responsible for ensuring those stay in scope for as long as + the structure is needed +- When calling Magnum's own APIs on the instance that was constructed from a + raw Vulkan structure, the pointed-to-data aren't touched in any way (which + means they can be @cpp const @ce), however they might get abandoned and the + top-level structure pointed elsewhere. +- Existing `pNext` chains are preserved. Calling Magnum's own APIs may result + in new entries added to the chain, but always at the front (again in order + to allow the pointed-to-data be @cpp const @ce). It's a responsibility of + the user to ensure no conflicting or duplicate entries are present + in the original chain when mixing it with Magnum's APIs. + +It's also possible to add new structures to the `pNext` chain of an existing +instance. However, to prevent conflicts with Magnum which inserts at the front, +new raw structures should be always appended at the end of the chain. @section vulkan-wrapping-optimizing-properties Optimizing instance and device property retrieval diff --git a/src/Magnum/Vk/Device.cpp b/src/Magnum/Vk/Device.cpp index 8a9e91182..9a35dc4cd 100644 --- a/src/Magnum/Vk/Device.cpp +++ b/src/Magnum/Vk/Device.cpp @@ -39,6 +39,7 @@ #include "Magnum/Vk/Assert.h" #include "Magnum/Vk/Handle.h" #include "Magnum/Vk/Instance.h" +#include "Magnum/Vk/DeviceFeatures.h" #include "Magnum/Vk/DeviceProperties.h" #include "Magnum/Vk/Extensions.h" #include "Magnum/Vk/ExtensionProperties.h" @@ -46,15 +47,46 @@ #include "Magnum/Vk/Version.h" #include "Magnum/Vk/Implementation/Arguments.h" #include "Magnum/Vk/Implementation/InstanceState.h" +#include "Magnum/Vk/Implementation/DeviceFeatures.h" #include "Magnum/Vk/Implementation/DeviceState.h" +#include "Magnum/Vk/Implementation/structureHelpers.h" #include "MagnumExternal/Vulkan/flextVkGlobal.h" namespace Magnum { namespace Vk { +/* In any other CreateInfo, we could simply populate a pNext chain of a + supported / enabled subset of all structures that might ever get used and + then only populate their contents without having to fumble with the linked + list connections. That's unfortunately not possible with DeviceCreateInfo, + because *don't know yet* what extensions will get enabled. So for everything + that might live on the pNext chain (currently just features, but over time + it'll be also multi-device setup, swapchain, ...) we need to: + + - ensure we're not stomping on something in pNext that's defined + externally (when constructing DeviceCreateInfo from a raw + VkDeviceCreateInfo or when the user directly adds something to pNext), + thus only connecting things to _info.pNext, not anywhere else as that + might be const memory + - ensure the externally supplied pNext pointers are not lost when we + connect our own things + - ensure when e.g. setEnabledFeatures() gets called twice, we don't + connect the same structure chain again, ending up with a loop. Which + means going through the existing chain and breaking up links that point + to the structures we're going to reconnect, this isn't really fast but + also it's not really common to call the same API more than once. This + also assumes that our structures are pointed to only by our structures + again and not something external (that might be const memory again). + +*/ struct DeviceCreateInfo::State { Containers::Array ownedStrings; Containers::Array extensions; + VkPhysicalDeviceFeatures2 features2{}; + Implementation::DeviceFeatures features{}; + DeviceFeatures enabledFeatures; + void* firstEnabledFeature{}; + Containers::String disabledExtensionsStorage; Containers::Array disabledExtensions; Containers::Array queues; @@ -133,11 +165,15 @@ DeviceCreateInfo::DeviceCreateInfo(DeviceProperties& deviceProperties, const Ext /* Conservatively populate the device properties. - In case the DeviceCreateInfo(DeviceProperties&&) constructor is used, it'll get overwritten straight away with a populated instance. - - In case the addQueues(QueueFlags) API is not used and DeviceCreateInfo - isn't subsequently moved to the Device, it'll never get touched again - and Device will wrap() its own. - - In case addQueues(QueueFlags) is used it'll get populated and then - possibly discarded if it isn't subsequently moved to the Device. */ + - In case neither the addQueues(QueueFlags) API (which queries the + properties for queue family index) nor the setEnabledFeatures() API + (which needs to check where to connect based on version and KHR_gpdp2 + presence) is not used and DeviceCreateInfo isn't subsequently moved to + the Device, it'll never get touched again and Device will wrap() its + own. + - In case addQueues(QueueFlags) / setEnabledFeatures() is used it'll get + populated and then possibly discarded if it isn't subsequently moved + to the Device. */ _state->properties = DeviceProperties::wrap(*deviceProperties._instance, deviceProperties._handle); } @@ -256,6 +292,168 @@ DeviceCreateInfo&& DeviceCreateInfo::addEnabledExtensions(const std::initializer return std::move(addEnabledExtensions(extensions)); } +namespace { + +template void structureConnectIfUsed(Containers::Reference& next, void*& firstFeatureStructure, T& structure, VkStructureType type) { + if(structure.sType) { + if(!firstFeatureStructure) firstFeatureStructure = &structure; + Implementation::structureConnect(next, structure, type); + } +} + +} + +DeviceCreateInfo& DeviceCreateInfo::setEnabledFeatures(const DeviceFeatures& features) & { + /* Remember the features to pass them to Device later */ + _state->enabledFeatures = features; + + /* Clear any existing pointers to the feature structure chain. This needs + to be done in order to avoid pointing to them again from a different + place, creating a loop. Additionally, the pNext chain may contain + additional structures after the features and we don't want to lose those + -- so it's not possible to simply disconnect and clear them, but we need + to first find and preserve what is connected after. + + To avoid quadratic complexity by going through each of the feature + structs and attempting to find it in the pNext chain, + _state->firstEnabledFeature remembers the first structure in the chain + that was enabled previously. We find what structure points to it and + then the structureDisconnectChain() goes through the chain and repoints + the structure to the first structure that's not from the list, thus + preserving the remaining part of the chain. */ + _info.pEnabledFeatures = nullptr; + if(_state->firstEnabledFeature) { + const void** const pointerToFirst = Implementation::structureFind(_info.pNext, *static_cast(_state->firstEnabledFeature)); + if(pointerToFirst) Implementation::structureDisconnectChain(*pointerToFirst, { + /* This list needs to be kept in sync with + Implementation::DeviceFeatures, keeping the same order (however + there's a test that should catch *all* errors with forgotten or + wrongly ordered structures) */ + _state->features2, + _state->features.protectedMemory, + _state->features.multiview, + _state->features.shaderDrawParameters, + _state->features.textureCompressionAstcHdr, + _state->features.shaderFloat16Int8, + _state->features._16BitStorage, + _state->features.imagelessFramebuffer, + _state->features.variablePointers, + _state->features.samplerYcbcrConversion, + _state->features.descriptorIndexing, + _state->features.shaderSubgroupExtendedTypes, + _state->features._8BitStorage, + _state->features.shaderAtomicInt64, + _state->features.timelineSemaphore, + _state->features.vulkanMemoryModel, + _state->features.scalarBlockLayout, + _state->features.separateDepthStencilLayouts, + _state->features.uniformBufferStandardLayout, + _state->features.bufferDeviceAddress, + _state->features.hostQueryReset, + _state->features.indexTypeUint8 + }); + + _state->firstEnabledFeature = {}; + } + + /* Now that the feature chain is disconnected from the pNext chain, we can + safely clear it */ + _state->features2 = {}; + _state->features = {}; + + /* If there's no features to enable, exit */ + if(!features) return *this; + + /* Otherwise, first set enabled bits in each structure and remember which + structures have bits set */ + #define _c(value, field) \ + if(features & DeviceFeature::value) { \ + _state->features2.sType = VkStructureType(1); \ + _state->features2.features.field = VK_TRUE; \ + } + #define _cver(value, field, suffix, version) \ + if(features & DeviceFeature::value) { \ + _state->features.suffix.sType = VkStructureType(1); \ + _state->features.suffix.field = VK_TRUE; \ + } + #define _cext _cver + #include "Magnum/Vk/Implementation/deviceFeatureMapping.hpp" + #undef _c + #undef _cver + #undef _cext + + /* First handle compatibility with unextended Vulkan 1.0 -- there we can + only add VkPhysicalDeviceFeatures to pEnabledFeatures and have to ignore + the rest. */ + if(!_state->properties.canUseFeatures2ForDeviceCreation()) { + /* Only point to the structure if something was actually enabled there. + If not, there's no point in referencing it. */ + if(_state->features2.sType) + _info.pEnabledFeatures = &_state->features2.features; + return *this; + } + + /* Otherwise we can start from _info.pNext */ + Containers::Reference next = _info.pNext; + + /* Connect together all structures that have something enabled. That + includes the VkPhysicalDeviceFeatures2 -- if it doesn't have anything + enabled, it's not included in the chain at all. The + _state->firstEnabledFeature pointer points to the first enabled feature + which will be useful to clean up the previous state if + setEnabledFeatures() gets called again. */ + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.protectedMemory, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.multiview, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.shaderDrawParameters, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.textureCompressionAstcHdr, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES_EXT); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.shaderFloat16Int8, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features._16BitStorage, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.imagelessFramebuffer, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.variablePointers, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.samplerYcbcrConversion, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.descriptorIndexing, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.shaderSubgroupExtendedTypes, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features._8BitStorage, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.shaderAtomicInt64, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_ATOMIC_INT64_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.timelineSemaphore, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.vulkanMemoryModel, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.scalarBlockLayout, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.separateDepthStencilLayouts, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SEPARATE_DEPTH_STENCIL_LAYOUTS_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.uniformBufferStandardLayout, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_UNIFORM_BUFFER_STANDARD_LAYOUT_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.bufferDeviceAddress, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.hostQueryReset, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES); + structureConnectIfUsed(next, _state->firstEnabledFeature, + _state->features.indexTypeUint8, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INDEX_TYPE_UINT8_FEATURES_EXT); + + return *this; +} + +DeviceCreateInfo&& DeviceCreateInfo::setEnabledFeatures(const DeviceFeatures& features) && { + return std::move(setEnabledFeatures(features)); +} + DeviceCreateInfo& DeviceCreateInfo::addQueues(const UnsignedInt family, const Containers::ArrayView priorities, const Containers::ArrayView> output) & { CORRADE_ASSERT(!priorities.empty(), "Vk::DeviceCreateInfo::addQueues(): at least one queue priority has to be specified", *this); CORRADE_ASSERT(output.size() == priorities.size(), "Vk::DeviceCreateInfo::addQueues(): expected" << priorities.size() << "outuput queue references but got" << output.size(), *this); @@ -331,19 +529,19 @@ DeviceCreateInfo&& DeviceCreateInfo::addQueues(const VkDeviceQueueCreateInfo& in return std::move(addQueues(info)); } -Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const Containers::ArrayView enabledExtensions, const HandleFlags flags) { +Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const Containers::ArrayView enabledExtensions, const DeviceFeatures& enabledFeatures, const HandleFlags flags) { /* Compared to the constructor nothing is printed here as it would be just repeating what was passed to the constructor */ Device out{NoCreate}; out._handle = handle; out._flags = flags; out.initializeExtensions(enabledExtensions); - out.initialize(instance, version); + out.initialize(instance, version, enabledFeatures); return out; } -Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const std::initializer_list enabledExtensions, const HandleFlags flags) { - return wrap(instance, handle, version, Containers::arrayView(enabledExtensions), flags); +Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const std::initializer_list enabledExtensions, const DeviceFeatures& enabledFeatures, const HandleFlags flags) { + return wrap(instance, handle, version, Containers::arrayView(enabledExtensions), enabledFeatures, flags); } Device::Device(Instance& instance, const DeviceCreateInfo& info): Device{instance, info, DeviceProperties::wrap(instance, info._physicalDevice)} {} @@ -379,12 +577,20 @@ Device::Device(Instance& instance, const DeviceCreateInfo& info, DevicePropertie for(std::size_t i = 0, max = info->enabledExtensionCount; i != max; ++i) Debug{} << " " << info->ppEnabledExtensionNames[i]; } + + if(info._state->enabledFeatures) { + Debug{} << "Enabled features:"; + for(std::size_t i = 0, max = DeviceFeatures::Size*64; i != max; ++i) { + if(!(info._state->enabledFeatures & DeviceFeature(i))) continue; + Debug{} << " " << DeviceFeature(i); + } + } } MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(instance->CreateDevice(info._physicalDevice, info, nullptr, &_handle)); initializeExtensions({info->ppEnabledExtensionNames, info->enabledExtensionCount}); - initialize(instance, version); + initialize(instance, version, info._state->enabledFeatures); /* Extension-dependent state is initialized, now we can retrieve the queues from the device and save them to the outputs specified in addQueues(). @@ -467,9 +673,10 @@ template void Device::initializeExtensions(const Containers::ArrayView< } } -void Device::initialize(Instance& instance, const Version version) { - /* Init version, function pointers */ +void Device::initialize(Instance& instance, const Version version, const DeviceFeatures& enabledFeatures) { + /* Init version, features, function pointers */ _version = version; + _enabledFeatures = enabledFeatures; flextVkInitDevice(_handle, &_functionPointers, instance->GetDeviceProcAddr); /* Set up extension-dependent functionality */ diff --git a/src/Magnum/Vk/Device.h b/src/Magnum/Vk/Device.h index faa3be0ed..4706183a3 100644 --- a/src/Magnum/Vk/Device.h +++ b/src/Magnum/Vk/Device.h @@ -31,6 +31,7 @@ */ #include +#include #include #include "Magnum/Tags.h" @@ -90,8 +91,24 @@ checked with @ref isExtensionEnabled(). @snippet MagnumVk.cpp Device-creation-extensions -Usually you'll be first checking for extension availability instead, which is -again accessible through the @ref DeviceProperties instance: +In addition to extensions, you'll be usually enabling features as well. These +are all exposed in a giant @ref DeviceFeatures enum and you can simply OR them +together. Internally, those get translated to @type_vk{PhysicalDeviceFeatures2} +and related structures, features that are not exposed in the enum can be +enabled by adding a corresponding structure to the `pNext` chain. As with +extensions, the set of enabled features can be later checked with +@ref enabledFeatures(). + +@snippet MagnumVk.cpp Device-creation-features + +However, usually you'll be checking for extension and feature availability +first, which is doable through +@ref DeviceProperties::enumerateExtensionProperties() and +@ref ExtensionProperties::isSupported() for extensions, and +@ref DeviceProperties::features() for features. In case of features you can +make use of the enum set operations and simply mask away features that are not +available --- however note that some features also require an extension to be +explicitly enabled. @snippet MagnumVk.cpp Device-creation-check-supported @@ -154,14 +171,18 @@ class MAGNUM_VK_EXPORT Device { * device * @param enabledExtensions Extensions that are assumed to be enabled * on the device + * @param enabledFeatures Features that are assumed to be enabled on + * the device * @param flags Handle flags * * The @p handle is expected to be originating from @p instance. The - * @p version and @p enabledExtensions parameters populate internal - * info about supported version and extensions and will be reflected in - * @ref isVersionSupported() and @ref isExtensionEnabled(), among other - * things. If @p enabledExtensions is empty, the device will behave as - * if no extensions were enabled. + * @p version, @p enabledExtensions and @p enabledFeatures parameters + * populate internal info about supported version, enabled extensions + * and enabled features and will be reflected in + * @ref isVersionSupported(), @ref isExtensionEnabled() and + * @ref enabledFeatures(), among other things. If @p enabledExtensions + * / @p enabledFeatures is empty, the device will behave as if no + * extensions / no features were enabled. * * Note that this function retrieves all device-specific Vulkan * function pointers, which is a relatively costly operation. It's thus @@ -173,10 +194,10 @@ class MAGNUM_VK_EXPORT Device { * behavior. * @see @ref release() */ - static Device wrap(Instance& instance, VkDevice handle, Version version, Containers::ArrayView enabledExtensions, HandleFlags flags = {}); + static Device wrap(Instance& instance, VkDevice handle, Version version, Containers::ArrayView enabledExtensions, const DeviceFeatures& enabledFeatures, HandleFlags flags = {}); /** @overload */ - static Device wrap(Instance& instance, VkDevice handle, Version version, std::initializer_list enabledExtensions, HandleFlags flags = {}); + static Device wrap(Instance& instance, VkDevice handle, Version version, std::initializer_list enabledExtensions, const DeviceFeatures& enabledFeatures, HandleFlags flags = {}); /** * @brief Constructor @@ -296,6 +317,13 @@ class MAGNUM_VK_EXPORT Device { /** @overload */ bool isExtensionEnabled(const Extension& extension) const; + /** + * @brief Features enabled on the device + * + * @see @ref DeviceProperties::features() + */ + const DeviceFeatures& enabledFeatures() const { return _enabledFeatures; } + /** * @brief Device-specific Vulkan function pointers * @@ -345,7 +373,7 @@ class MAGNUM_VK_EXPORT Device { explicit Device(Instance& isntance, const DeviceCreateInfo&, DeviceProperties&&); template MAGNUM_VK_LOCAL void initializeExtensions(Containers::ArrayView enabledExtensions); - MAGNUM_VK_LOCAL void initialize(Instance& instance, Version version); + MAGNUM_VK_LOCAL void initialize(Instance& instance, Version version, const DeviceFeatures& enabledFeatures); MAGNUM_VK_LOCAL static void getQueueImplementationDefault(Device& self, const VkDeviceQueueInfo2& info, VkQueue& queue); MAGNUM_VK_LOCAL static void getQueueImplementation11(Device& self, const VkDeviceQueueInfo2& info, VkQueue& queue); @@ -354,6 +382,7 @@ class MAGNUM_VK_EXPORT Device { HandleFlags _flags; Version _version; Math::BoolVector _enabledExtensions; + DeviceFeatures _enabledFeatures; Containers::Pointer _properties; Containers::Pointer _state; diff --git a/src/Magnum/Vk/DeviceCreateInfo.h b/src/Magnum/Vk/DeviceCreateInfo.h index 2a017cb34..2939941c9 100644 --- a/src/Magnum/Vk/DeviceCreateInfo.h +++ b/src/Magnum/Vk/DeviceCreateInfo.h @@ -200,6 +200,65 @@ class MAGNUM_VK_EXPORT DeviceCreateInfo { return std::move(*this); } + /** + * @brief Add enabled device features + * @return Reference to self (for method chaining) + * + * All enabled features are expected to be reported as supported by the + * device and either their core version supported by the device or the + * corresponding extension enabled via @ref addEnabledExtensions(). Use + * @ref DeviceProperties::features() to check for feature support. + * + * Depending on what features are enabled, a subset of the following + * structures will be added to the `pNext` chain: + * + * - @type_vk_keyword{PhysicalDeviceProtectedMemoryFeatures} (Vulkan + * 1.1) + * - @type_vk_keyword{PhysicalDeviceMultiviewFeatures} (Vulkan 1.1, + * @vk_extension{KHR,multiview}) + * - @type_vk_keyword{PhysicalDeviceShaderDrawParametersFeatures} + * (Vulkan 1.1, @vk_extension{KHR,shader_draw_parameters}) + * - @type_vk_keyword{PhysicalDeviceTextureCompressionASTCHDRFeaturesEXT} + * (@vk_extension{EXT,texture_compression_astc_hdr}) + * - @type_vk_keyword{PhysicalDeviceShaderFloat16Int8Features} + * (Vulkan 1.2, @vk_extension{KHR,shader_float16_int8}) + * - @type_vk_keyword{PhysicalDevice16BitStorageFeatures} (Vulkan + * 1.1, @vk_extension{KHR,16bit_storage}) + * - @type_vk_keyword{PhysicalDeviceImagelessFramebufferFeatures} + * (Vulkan 1.2, @vk_extension{KHR,imageless_framebuffer}) + * - @type_vk_keyword{PhysicalDeviceVariablePointersFeatures} + * (Vulkan 1.1, @vk_extension{KHR,variable_pointers}) + * - @type_vk_keyword{PhysicalDeviceSamplerYcbcrConversionFeatures} + * (Vulkan 1.1, @vk_extension{KHR,sampler_ycbcr_conversion}) + * - @type_vk_keyword{PhysicalDeviceDescriptorIndexingFeatures} + * (Vulkan 1.2, @vk_extension{EXT,descriptor_indexing}) + * - @type_vk_keyword{PhysicalDeviceShaderSubgroupExtendedTypesFeatures} + * (Vulkan 1.2, @vk_extension{KHR,shader_subgroup_extended_types}) + * - @type_vk_keyword{PhysicalDevice8BitStorageFeatures} (Vulkan + * 1.2, @vk_extension{KHR,8bit_storage}) + * - @type_vk_keyword{PhysicalDeviceShaderAtomicInt64Features} + * (Vulkan 1.2, @vk_extension{KHR,shader_atomic_int64}) + * - @type_vk_keyword{PhysicalDeviceTimelineSemaphoreFeatures} + * (Vulkan 1.2, @vk_extension{KHR,timeline_semaphore}) + * - @type_vk_keyword{PhysicalDeviceVulkanMemoryModelFeatures} + * (Vulkan 1.2, @vk_extension{KHR,vulkan_memory_model}) + * - @type_vk_keyword{PhysicalDeviceScalarBlockLayoutFeatures} + * (Vulkan 1.2, @vk_extension{EXT,scalar_block_layout}) + * - @type_vk_keyword{PhysicalDeviceSeparateDepthStencilLayoutsFeatures} + * (Vulkan 1.2, @vk_extension{KHR,separate_depth_stencil_layouts}) + * - @type_vk_keyword{PhysicalDeviceUniformBufferStandardLayoutFeatures} + * (Vulkan 1.2, @vk_extension{KHR,uniform_buffer_standard_layout}) + * - @type_vk_keyword{PhysicalDeviceBufferDeviceAddressFeatures} + * (Vulkan 1.2, @vk_extension{KHR,buffer_device_address}) + * - @type_vk_keyword{PhysicalDeviceHostQueryResetFeatures} (Vulkan + * 1.2, @vk_extension{EXT,host_query_reset}) + * - @type_vk_keyword{PhysicalDeviceIndexTypeUint8FeaturesEXT} + * (@vk_extension{EXT,index_type_uint8}) + */ + DeviceCreateInfo& setEnabledFeatures(const DeviceFeatures& features) &; + /** @overload */ + DeviceCreateInfo&& setEnabledFeatures(const DeviceFeatures& features) &&; + /** * @brief Add queues * @param[in] family Family index, smaller than diff --git a/src/Magnum/Vk/DeviceProperties.cpp b/src/Magnum/Vk/DeviceProperties.cpp index 79ac31d8e..774ab50f5 100644 --- a/src/Magnum/Vk/DeviceProperties.cpp +++ b/src/Magnum/Vk/DeviceProperties.cpp @@ -259,6 +259,21 @@ template bool DeviceProperties::isOrVersionSupportedInternal() { return extensionPropertiesInternal().isSupported(); } +bool DeviceProperties::canUseFeatures2ForDeviceCreation() { + if(!_state) _state.emplace(*_instance, _handle); + + /* To avoid repeating the logic (and the 10-paragraph explanation) from + State constructor here, we simply check what is used to query device + features. If the 1.1 or KHR entry point then we can, if the default then + we can't. */ + if(_state->getFeaturesImplementation == getFeaturesImplementation11 || + _state->getFeaturesImplementation == getFeaturesImplementationKHR) + return true; + if(_state->getFeaturesImplementation == getFeaturesImplementationDefault) + return false; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + const DeviceFeatures& DeviceProperties::features() { if(!_state) _state.emplace(*_instance, _handle); diff --git a/src/Magnum/Vk/DeviceProperties.h b/src/Magnum/Vk/DeviceProperties.h index 7e0eb70a5..f6402b8e7 100644 --- a/src/Magnum/Vk/DeviceProperties.h +++ b/src/Magnum/Vk/DeviceProperties.h @@ -482,7 +482,8 @@ class MAGNUM_VK_EXPORT DeviceProperties { * supported by the device, the `pNext` chain contains * @type_vk_keyword{PhysicalDeviceIndexTypeUint8FeaturesEXT} * - * @see @fn_vk_keyword{GetPhysicalDeviceFeatures2}, + * @see @ref Device::enabledFeatures(), + * @fn_vk_keyword{GetPhysicalDeviceFeatures2}, * @fn_vk_keyword{GetPhysicalDeviceFeatures}, * @type_vk_keyword{PhysicalDeviceFeatures2}, * @type_vk_keyword{PhysicalDeviceFeatures} @@ -715,6 +716,9 @@ class MAGNUM_VK_EXPORT DeviceProperties { accidents with incorrectly specified extension core version. */ template MAGNUM_VK_LOCAL bool isOrVersionSupportedInternal(); + /* Used by DeviceCreateInfo */ + MAGNUM_VK_LOCAL bool canUseFeatures2ForDeviceCreation(); + MAGNUM_VK_LOCAL static void getPropertiesImplementationDefault(DeviceProperties& self, VkPhysicalDeviceProperties2& properties); MAGNUM_VK_LOCAL static void getPropertiesImplementationKHR(DeviceProperties& self, VkPhysicalDeviceProperties2& properties); MAGNUM_VK_LOCAL static void getPropertiesImplementation11(DeviceProperties& self, VkPhysicalDeviceProperties2& properties); diff --git a/src/Magnum/Vk/Implementation/DeviceFeatures.h b/src/Magnum/Vk/Implementation/DeviceFeatures.h index c601f6f30..ea4de0ef5 100644 --- a/src/Magnum/Vk/Implementation/DeviceFeatures.h +++ b/src/Magnum/Vk/Implementation/DeviceFeatures.h @@ -31,7 +31,10 @@ namespace Magnum { namespace Vk { namespace Implementation { /* Everything except the top-level VkPhysicalDeviceFeatures2 structure, which is treated differently when querying and enabling the features. Used by - DeviceProperties.cpp. */ + DeviceProperties.cpp and Device.cpp, in addition needs to be kept in sync + with the list in Device.cpp passed to Implementation::structureDisconnectChain() + (however there's a test that should catch *all* errors with forgotten or + wrongly ordered structures there). */ struct DeviceFeatures { VkPhysicalDeviceProtectedMemoryFeatures protectedMemory; VkPhysicalDeviceMultiviewFeatures multiview; diff --git a/src/Magnum/Vk/Test/DeviceVkTest.cpp b/src/Magnum/Vk/Test/DeviceVkTest.cpp index 5cf4f7c39..9e05ce252 100644 --- a/src/Magnum/Vk/Test/DeviceVkTest.cpp +++ b/src/Magnum/Vk/Test/DeviceVkTest.cpp @@ -31,6 +31,7 @@ #include #include "Magnum/Vk/DeviceCreateInfo.h" +#include "Magnum/Vk/DeviceFeatures.h" #include "Magnum/Vk/DeviceProperties.h" #include "Magnum/Vk/Extensions.h" #include "Magnum/Vk/ExtensionProperties.h" @@ -53,6 +54,11 @@ struct DeviceVkTest: VulkanTester { void createInfoConstructNoImplicitExtensions(); void createInfoExtensions(); void createInfoExtensionsCopiedStrings(); + void createInfoFeatures(); + void createInfoFeaturesReplaceExternal(); + void createInfoFeaturesReplacePrevious(); + void createInfoFeaturesEnableAllResetAll(); + void createInfoFeaturesNothingInCoreFeatures(); void createInfoNoQueuePriorities(); void createInfoWrongQueueOutputCount(); void createInfoConstructCopy(); @@ -62,6 +68,8 @@ struct DeviceVkTest: VulkanTester { void construct(); void constructQueueFromFlags(); void constructExtensions(); + void constructFeatures(); + void constructFeaturesFromExtensions(); void constructDeviceCreateInfoConstReference(); void constructTransferDeviceProperties(); void constructExtensionsCommandLineDisable(); @@ -70,6 +78,8 @@ struct DeviceVkTest: VulkanTester { void constructRawQueue(); void constructMove(); void constructUnknownExtension(); + void constructFeatureNotSupported(); + void constructFeatureWithoutExtension(); void constructNoQueue(); void wrap(); @@ -137,6 +147,11 @@ DeviceVkTest::DeviceVkTest(): VulkanTester{NoCreate} { &DeviceVkTest::createInfoConstructNoImplicitExtensions, &DeviceVkTest::createInfoExtensions, &DeviceVkTest::createInfoExtensionsCopiedStrings, + &DeviceVkTest::createInfoFeatures, + &DeviceVkTest::createInfoFeaturesReplaceExternal, + &DeviceVkTest::createInfoFeaturesReplacePrevious, + &DeviceVkTest::createInfoFeaturesEnableAllResetAll, + &DeviceVkTest::createInfoFeaturesNothingInCoreFeatures, &DeviceVkTest::createInfoNoQueuePriorities, &DeviceVkTest::createInfoWrongQueueOutputCount, &DeviceVkTest::createInfoConstructCopy, @@ -146,6 +161,8 @@ DeviceVkTest::DeviceVkTest(): VulkanTester{NoCreate} { &DeviceVkTest::construct, &DeviceVkTest::constructQueueFromFlags, &DeviceVkTest::constructExtensions, + &DeviceVkTest::constructFeatures, + &DeviceVkTest::constructFeaturesFromExtensions, &DeviceVkTest::constructDeviceCreateInfoConstReference, &DeviceVkTest::constructTransferDeviceProperties}); @@ -158,6 +175,8 @@ DeviceVkTest::DeviceVkTest(): VulkanTester{NoCreate} { &DeviceVkTest::constructMove, &DeviceVkTest::constructUnknownExtension, + &DeviceVkTest::constructFeatureNotSupported, + &DeviceVkTest::constructFeatureWithoutExtension, &DeviceVkTest::constructNoQueue, &DeviceVkTest::wrap, @@ -171,6 +190,7 @@ void DeviceVkTest::createInfoConstruct() { CORRADE_VERIFY(info->sType); CORRADE_VERIFY(!info->pNext); /* Extensions might or might not be enabled */ + CORRADE_VERIFY(!info->pEnabledFeatures); } void DeviceVkTest::createInfoConstructNoImplicitExtensions() { @@ -180,6 +200,7 @@ void DeviceVkTest::createInfoConstructNoImplicitExtensions() { /* No extensions enabled as we explicitly disabled that */ CORRADE_VERIFY(!info->ppEnabledExtensionNames); CORRADE_COMPARE(info->enabledExtensionCount, 0); + CORRADE_VERIFY(!info->pEnabledFeatures); } void DeviceVkTest::createInfoExtensions() { @@ -229,6 +250,186 @@ void DeviceVkTest::createInfoExtensionsCopiedStrings() { CORRADE_VERIFY(info->ppEnabledExtensionNames[1] != localButNullTerminated.data()); } +void DeviceVkTest::createInfoFeatures() { + DeviceProperties properties = pickDevice(instance()); + + /* We don't use the structure for anything, so we don't need to check if + the structure is actually supported */ + DeviceCreateInfo info{properties}; + info.setEnabledFeatures(DeviceFeature::RobustBufferAccess|DeviceFeature::SamplerYcbcrConversion); + + /* If we have Vulkan 1.1 on both instance and the device or KHR_gpdp2 is + enabled on the instance, pNext chain will be filled as appropriate */ + if((instance().isVersionSupported(Version::Vk11) && properties.isVersionSupported(Version::Vk11)) || instance().isExtensionEnabled()) { + CORRADE_VERIFY(!info->pEnabledFeatures); + CORRADE_VERIFY(info->pNext); + const auto& features2 = *static_cast(info->pNext); + CORRADE_COMPARE(features2.sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2); + CORRADE_VERIFY(features2.features.robustBufferAccess); + + CORRADE_VERIFY(features2.pNext); + const auto& samplerYcbcrConversionFeatures = *static_cast(features2.pNext); + CORRADE_COMPARE(samplerYcbcrConversionFeatures.sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES); + CORRADE_VERIFY(samplerYcbcrConversionFeatures.samplerYcbcrConversion); + + /* Otherwise just the pEnabledFeatures will be enabled */ + } else { + CORRADE_VERIFY(!info->pNext); + CORRADE_VERIFY(info->pEnabledFeatures); + CORRADE_VERIFY(info->pEnabledFeatures->robustBufferAccess); + } +} + +void DeviceVkTest::createInfoFeaturesNothingInCoreFeatures() { + DeviceProperties properties = pickDevice(instance()); + + DeviceCreateInfo info{properties}; + info.setEnabledFeatures(DeviceFeature::SamplerYcbcrConversion|DeviceFeature::ImagelessFramebuffer); + + /* If we have Vulkan 1.1 on both instance and the device or KHR_gpdp2 is + enabled on the instance, pNext chain will be filled as appropriate */ + if((instance().isVersionSupported(Version::Vk11) && properties.isVersionSupported(Version::Vk11)) || instance().isExtensionEnabled()) { + CORRADE_VERIFY(!info->pEnabledFeatures); + CORRADE_VERIFY(info->pNext); + const auto& imagelessFramebufferFeatures = *static_cast(info->pNext); + CORRADE_COMPARE(imagelessFramebufferFeatures.sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES); + CORRADE_VERIFY(imagelessFramebufferFeatures.imagelessFramebuffer); + + const auto& samplerYcbcrConversionFeatures = *static_cast(imagelessFramebufferFeatures.pNext); + CORRADE_COMPARE(samplerYcbcrConversionFeatures.sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES); + CORRADE_VERIFY(samplerYcbcrConversionFeatures.samplerYcbcrConversion); + + /* Otherwise nothing is enabled as there's nowhere to connect that */ + } else { + CORRADE_VERIFY(!info->pNext); + CORRADE_VERIFY(!info->pEnabledFeatures); + } +} + +void DeviceVkTest::createInfoFeaturesReplaceExternal() { + DeviceProperties properties = pickDevice(instance()); + + VkPhysicalDeviceFeatures2 features{}; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + VkAttachmentReference2 somethingAfter{}; + somethingAfter.pNext = &features; + + DeviceCreateInfo info{properties}; + info->pNext = &somethingAfter; + info->pEnabledFeatures = &features.features; + + info.setEnabledFeatures(DeviceFeature::RobustBufferAccess); + + /* Then, if we have Vulkan 1.1 on both instance and the device or KHR_gpdp2 + is enabled on the instance, pNext will be filled and pEnabledFeatures + reset */ + if((instance().isVersionSupported(Version::Vk11) && properties.isVersionSupported(Version::Vk11)) || instance().isExtensionEnabled()) { + CORRADE_VERIFY(!info->pEnabledFeatures); + CORRADE_VERIFY(info->pNext); + const auto& features2 = *static_cast(info->pNext); + CORRADE_COMPARE(features2.sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2); + CORRADE_VERIFY(features2.features.robustBufferAccess); + + /* The original chain should be preserved after */ + CORRADE_COMPARE(features2.pNext, &somethingAfter); + + /* Otherwise the pNext chain will be preserved and pEnabledFeatures + replaced with own instance */ + } else { + CORRADE_VERIFY(info->pEnabledFeatures); + CORRADE_VERIFY(info->pEnabledFeatures != &features.features); + CORRADE_VERIFY(info->pEnabledFeatures->robustBufferAccess); + } + + /* No changes to the original chain, even though it has a features on its + own (that's user error) */ + CORRADE_COMPARE(somethingAfter.pNext, &features); +} + +void DeviceVkTest::createInfoFeaturesReplacePrevious() { + DeviceProperties properties = pickDevice(instance()); + + VkAttachmentReference2 somethingAfter{}; + + DeviceCreateInfo info{properties}; + info->pNext = &somethingAfter; + + info.setEnabledFeatures(DeviceFeature::RobustBufferAccess); + + /* If we have Vulkan 1.1 on both instance and the device or KHR_gpdp2 is + enabled on the instance, pNext chain will be filled as appropriate */ + if((instance().isVersionSupported(Version::Vk11) && properties.isVersionSupported(Version::Vk11)) || instance().isExtensionEnabled()) { + CORRADE_VERIFY(!info->pEnabledFeatures); + CORRADE_VERIFY(info->pNext); + const auto& features2 = *static_cast(info->pNext); + CORRADE_COMPARE(features2.sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2); + CORRADE_VERIFY(features2.features.robustBufferAccess); + + /* The original chain should be preserved after */ + CORRADE_COMPARE(features2.pNext, &somethingAfter); + + /* Otherwise the pNext chain will be preserved and pEnabledFeatures + replaced with own instance */ + } else { + CORRADE_COMPARE(info->pNext, &somethingAfter); + CORRADE_VERIFY(info->pEnabledFeatures); + CORRADE_VERIFY(info->pEnabledFeatures->robustBufferAccess); + } + + /* Setting a different non-core feature */ + info.setEnabledFeatures(DeviceFeature::ImagelessFramebuffer); + if((instance().isVersionSupported(Version::Vk11) && properties.isVersionSupported(Version::Vk11)) || instance().isExtensionEnabled()) { + CORRADE_VERIFY(!info->pEnabledFeatures); + CORRADE_VERIFY(info->pNext); + const auto& imagelessFramebufferFeatures = *static_cast(info->pNext); + CORRADE_COMPARE(imagelessFramebufferFeatures.sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES); + CORRADE_VERIFY(imagelessFramebufferFeatures.imagelessFramebuffer); + + /* The original chain should still be preserved after, without the + structures from the previous case getting in the way */ + CORRADE_COMPARE(imagelessFramebufferFeatures.pNext, &somethingAfter); + + /* Otherwise the pNext chain will still be preserved and pEnabledFeatures + empty */ + } else { + CORRADE_COMPARE(info->pNext, &somethingAfter); + CORRADE_VERIFY(!info->pEnabledFeatures); + } + + /* Setting no features, everything should be fully discarded, and the + original chain still kept. This doesn't have any difference between + versions. */ + info.setEnabledFeatures({}); + CORRADE_COMPARE(info->pNext, &somethingAfter); + CORRADE_VERIFY(!info->pEnabledFeatures); + CORRADE_VERIFY(!somethingAfter.pNext); +} + +void DeviceVkTest::createInfoFeaturesEnableAllResetAll() { + DeviceProperties properties = pickDevice(instance()); + + if((!instance().isVersionSupported(Version::Vk11) || !properties.isVersionSupported(Version::Vk11)) && !instance().isExtensionEnabled()) + CORRADE_SKIP("Neither Vulkan 1.1 nor KHR_get_physical_device_properties2 is supported, can't test"); + + VkAttachmentDescription2 somethingAfter{}; + somethingAfter.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; + + DeviceCreateInfo info{properties}; + info->pNext = &somethingAfter; + + /* This should populate a huge chain of structures */ + info.setEnabledFeatures(~DeviceFeatures{}); + CORRADE_VERIFY(info->pNext != &somethingAfter); + CORRADE_VERIFY(!somethingAfter.pNext); + + /* And this should disconnect them all again. If this fails, it means the + resetting code path got out of sync with the structure list. Sorry, + there's not really a better way how to show *where* it got wrong. */ + info.setEnabledFeatures({}); + CORRADE_COMPARE(info->pNext, &somethingAfter); + CORRADE_VERIFY(!somethingAfter.pNext); +} + void DeviceVkTest::createInfoNoQueuePriorities() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -417,6 +618,40 @@ void DeviceVkTest::constructExtensions() { CORRADE_VERIFY(device->TrimCommandPoolKHR); } +void DeviceVkTest::constructFeatures() { + Queue queue{NoCreate}; + Device device{instance(), DeviceCreateInfo{pickDevice(instance())} + .addQueues(0, {0.0f}, {queue}) + /* RobustBufferAccess is guaranteed to be supported always, no need to + check anything */ + .setEnabledFeatures(DeviceFeature::RobustBufferAccess)}; + CORRADE_VERIFY(device.handle()); + + /* Features should be reported as enabled */ + CORRADE_COMPARE(device.enabledFeatures(), DeviceFeature::RobustBufferAccess); +} + +void DeviceVkTest::constructFeaturesFromExtensions() { + DeviceProperties properties = pickDevice(instance()); + + if(!properties.enumerateExtensionProperties().isSupported()) + CORRADE_SKIP("VK_KHR_sampler_ycbcr_conversion not supported, can't test"); + if(!(properties.features() & DeviceFeature::SamplerYcbcrConversion)) + CORRADE_SKIP("SamplerYcbcrConversion feature not supported, can't test"); + + Queue queue{NoCreate}; + Device device{instance(), DeviceCreateInfo{properties} + .addQueues(0, {0.0f}, {queue}) + .addEnabledExtensions() + /* RobustBufferAccess is guaranteed to be supported always, no need to + check anything */ + .setEnabledFeatures(DeviceFeature::RobustBufferAccess|DeviceFeature::SamplerYcbcrConversion)}; + CORRADE_VERIFY(device.handle()); + + /* Features should be reported as enabled */ + CORRADE_COMPARE(device.enabledFeatures(), DeviceFeature::RobustBufferAccess|DeviceFeature::SamplerYcbcrConversion); +} + void DeviceVkTest::constructDeviceCreateInfoConstReference() { Queue queue{NoCreate}; DeviceProperties deviceProperties = pickDevice(instance()); @@ -699,6 +934,45 @@ void DeviceVkTest::constructUnknownExtension() { CORRADE_COMPARE(out.str(), "TODO"); } +void DeviceVkTest::constructFeatureNotSupported() { + DeviceProperties properties = pickDevice(instance()); + if(properties.features() & DeviceFeature::SparseBinding) + CORRADE_SKIP("The SparseBinding feature is supported, can't test"); + CORRADE_SKIP("Currently this hits an internal assert, which can't be tested."); + + std::ostringstream out; + Error redirectError{&out}; + Queue queue{NoCreate}; + Device device{instance(), DeviceCreateInfo{properties} + .addQueues(0, {0.0f}, {queue}) + .setEnabledFeatures(DeviceFeature::SparseBinding)}; + CORRADE_COMPARE(out.str(), "TODO"); +} + +void DeviceVkTest::constructFeatureWithoutExtension() { + DeviceProperties properties = pickDevice(instance()); + if((!instance().isVersionSupported(Version::Vk11) || !properties.isVersionSupported(Version::Vk11)) && !instance().isExtensionEnabled()) + CORRADE_SKIP("Neither Vulkan 1.1 nor KHR_get_physical_device_properties2 is supported, can't test"); + if(properties.features() & DeviceFeature::TextureCompressionAstcHdr) + CORRADE_SKIP("The TextureCompressionAstcHdr feature is supported, can't test"); + + Queue queue{NoCreate}; + DeviceCreateInfo info{properties}; + info.addQueues(0, {0.0f}, {queue}) + .setEnabledFeatures(DeviceFeature::TextureCompressionAstcHdr); + + /* Just to verify we're doing the correct thing */ + CORRADE_VERIFY(info->pNext); + CORRADE_COMPARE(static_cast(info->pNext)->sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES_EXT); + CORRADE_VERIFY(static_cast(info->pNext)->textureCompressionASTC_HDR); + + std::ostringstream out; + Error redirectError{&out}; + Device device{instance(), info}; + CORRADE_EXPECT_FAIL("For some reason it doesn't complain when a feature that needs an extension is enabled. Am I stupid?"); + CORRADE_VERIFY(!out.str().empty()); +} + void DeviceVkTest::constructNoQueue() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); @@ -743,7 +1017,10 @@ void DeviceVkTest::wrap() { .addEnabledExtensions< Extensions::EXT::debug_marker, Extensions::KHR::maintenance1 - >(), + >() + /* RobustBufferAccess is guaranteed to be supported always, no need + to check anything */ + .setEnabledFeatures(DeviceFeature::RobustBufferAccess), nullptr, &device)), Result::Success); CORRADE_VERIFY(device); /* Populating the queue handle is done only from Device itself, so it won't @@ -754,7 +1031,7 @@ void DeviceVkTest::wrap() { /* Wrapping should load the basic function pointers */ auto wrapped = Device::wrap(instance2, device, Version::Vk11, { Extensions::EXT::debug_marker::string() - }, HandleFlag::DestroyOnDestruction); + }, DeviceFeature::RobustBufferAccess, HandleFlag::DestroyOnDestruction); CORRADE_VERIFY(wrapped->DestroyDevice); /* Specified version should be reported as supported but higher not @@ -772,12 +1049,15 @@ void DeviceVkTest::wrap() { CORRADE_VERIFY(!wrapped.isExtensionEnabled()); CORRADE_VERIFY(wrapped->TrimCommandPoolKHR); + /* Listed features should be reported as enabled */ + CORRADE_COMPARE(wrapped.enabledFeatures(), DeviceFeature::RobustBufferAccess); + /* Releasing won't destroy anything ... */ CORRADE_COMPARE(wrapped.release(), device); } /* ...so we can wrap it again, non-owned, and then destroy it manually */ - auto wrapped = Device::wrap(instance2, device, Version::Vk10, {}); + auto wrapped = Device::wrap(instance2, device, Version::Vk10, {}, {}); CORRADE_VERIFY(wrapped->DestroyDevice); wrapped->DestroyDevice(device, nullptr); }