Browse Source

Vk: implement Device feature enablement.

This was way more pain that initially expected, especially in regards to
preserving externally-specified pNext chains without writing to them in
any way.
pull/491/head
Vladimír Vondruš 5 years ago
parent
commit
aad588054a
  1. 7
      doc/developers.dox
  2. 22
      doc/snippets/MagnumVk.cpp
  3. 19
      doc/vulkan-wrapping.dox
  4. 231
      src/Magnum/Vk/Device.cpp
  5. 49
      src/Magnum/Vk/Device.h
  6. 59
      src/Magnum/Vk/DeviceCreateInfo.h
  7. 15
      src/Magnum/Vk/DeviceProperties.cpp
  8. 6
      src/Magnum/Vk/DeviceProperties.h
  9. 5
      src/Magnum/Vk/Implementation/DeviceFeatures.h
  10. 286
      src/Magnum/Vk/Test/DeviceVkTest.cpp

7
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 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 `sType` fields in this query so it's easy to make a hard-to-discover
error 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 @section developers-dependency Checklist for adding, removing or updating a dependency

22
doc/snippets/MagnumVk.cpp

@ -34,6 +34,7 @@
#include "Magnum/Vk/CommandBuffer.h" #include "Magnum/Vk/CommandBuffer.h"
#include "Magnum/Vk/CommandPoolCreateInfo.h" #include "Magnum/Vk/CommandPoolCreateInfo.h"
#include "Magnum/Vk/DeviceCreateInfo.h" #include "Magnum/Vk/DeviceCreateInfo.h"
#include "Magnum/Vk/DeviceFeatures.h"
#include "Magnum/Vk/DeviceProperties.h" #include "Magnum/Vk/DeviceProperties.h"
#include "Magnum/Vk/Extensions.h" #include "Magnum/Vk/Extensions.h"
#include "Magnum/Vk/ExtensionProperties.h" #include "Magnum/Vk/ExtensionProperties.h"
@ -214,6 +215,22 @@ Vk::Device device{instance, Vk::DeviceCreateInfo{DOXYGEN_IGNORE(properties)}
/* [Device-creation-extensions] */ /* [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; Vk::Instance instance;
using namespace Containers::Literals; using namespace Containers::Literals;
@ -227,6 +244,11 @@ if(extensions.isSupported<Vk::Extensions::EXT::index_type_uint8>())
if(extensions.isSupported("VK_NV_mesh_shader"_s)) if(extensions.isSupported("VK_NV_mesh_shader"_s))
info.addEnabledExtensions({"VK_NV_mesh_shader"_s}); info.addEnabledExtensions({"VK_NV_mesh_shader"_s});
DOXYGEN_IGNORE() 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] */ /* [Device-creation-check-supported] */
} }

19
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` Similarly to the @ref NoInit constructors, constructing a `Vk::*CreateInfo`
from the underlying Vulkan structure is guaranteed to not allocate as well --- 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, 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 @section vulkan-wrapping-optimizing-properties Optimizing instance and device property retrieval

231
src/Magnum/Vk/Device.cpp

@ -39,6 +39,7 @@
#include "Magnum/Vk/Assert.h" #include "Magnum/Vk/Assert.h"
#include "Magnum/Vk/Handle.h" #include "Magnum/Vk/Handle.h"
#include "Magnum/Vk/Instance.h" #include "Magnum/Vk/Instance.h"
#include "Magnum/Vk/DeviceFeatures.h"
#include "Magnum/Vk/DeviceProperties.h" #include "Magnum/Vk/DeviceProperties.h"
#include "Magnum/Vk/Extensions.h" #include "Magnum/Vk/Extensions.h"
#include "Magnum/Vk/ExtensionProperties.h" #include "Magnum/Vk/ExtensionProperties.h"
@ -46,15 +47,46 @@
#include "Magnum/Vk/Version.h" #include "Magnum/Vk/Version.h"
#include "Magnum/Vk/Implementation/Arguments.h" #include "Magnum/Vk/Implementation/Arguments.h"
#include "Magnum/Vk/Implementation/InstanceState.h" #include "Magnum/Vk/Implementation/InstanceState.h"
#include "Magnum/Vk/Implementation/DeviceFeatures.h"
#include "Magnum/Vk/Implementation/DeviceState.h" #include "Magnum/Vk/Implementation/DeviceState.h"
#include "Magnum/Vk/Implementation/structureHelpers.h"
#include "MagnumExternal/Vulkan/flextVkGlobal.h" #include "MagnumExternal/Vulkan/flextVkGlobal.h"
namespace Magnum { namespace Vk { 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 { struct DeviceCreateInfo::State {
Containers::Array<Containers::String> ownedStrings; Containers::Array<Containers::String> ownedStrings;
Containers::Array<const char*> extensions; Containers::Array<const char*> extensions;
VkPhysicalDeviceFeatures2 features2{};
Implementation::DeviceFeatures features{};
DeviceFeatures enabledFeatures;
void* firstEnabledFeature{};
Containers::String disabledExtensionsStorage; Containers::String disabledExtensionsStorage;
Containers::Array<Containers::StringView> disabledExtensions; Containers::Array<Containers::StringView> disabledExtensions;
Containers::Array<VkDeviceQueueCreateInfo> queues; Containers::Array<VkDeviceQueueCreateInfo> queues;
@ -133,11 +165,15 @@ DeviceCreateInfo::DeviceCreateInfo(DeviceProperties& deviceProperties, const Ext
/* Conservatively populate the device properties. /* Conservatively populate the device properties.
- In case the DeviceCreateInfo(DeviceProperties&&) constructor is used, - In case the DeviceCreateInfo(DeviceProperties&&) constructor is used,
it'll get overwritten straight away with a populated instance. it'll get overwritten straight away with a populated instance.
- In case the addQueues(QueueFlags) API is not used and DeviceCreateInfo - In case neither the addQueues(QueueFlags) API (which queries the
isn't subsequently moved to the Device, it'll never get touched again properties for queue family index) nor the setEnabledFeatures() API
and Device will wrap() its own. (which needs to check where to connect based on version and KHR_gpdp2
- In case addQueues(QueueFlags) is used it'll get populated and then presence) is not used and DeviceCreateInfo isn't subsequently moved to
possibly discarded if it isn't subsequently moved to the Device. */ 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); _state->properties = DeviceProperties::wrap(*deviceProperties._instance, deviceProperties._handle);
} }
@ -256,6 +292,168 @@ DeviceCreateInfo&& DeviceCreateInfo::addEnabledExtensions(const std::initializer
return std::move(addEnabledExtensions(extensions)); return std::move(addEnabledExtensions(extensions));
} }
namespace {
template<class T> void structureConnectIfUsed(Containers::Reference<const void*>& 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<const VkBaseInStructure*>(_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<const void*> 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<const Float> priorities, const Containers::ArrayView<const Containers::Reference<Queue>> output) & { DeviceCreateInfo& DeviceCreateInfo::addQueues(const UnsignedInt family, const Containers::ArrayView<const Float> priorities, const Containers::ArrayView<const Containers::Reference<Queue>> output) & {
CORRADE_ASSERT(!priorities.empty(), "Vk::DeviceCreateInfo::addQueues(): at least one queue priority has to be specified", *this); 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); 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)); return std::move(addQueues(info));
} }
Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const Containers::ArrayView<const Containers::StringView> enabledExtensions, const HandleFlags flags) { Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const Containers::ArrayView<const Containers::StringView> enabledExtensions, const DeviceFeatures& enabledFeatures, const HandleFlags flags) {
/* Compared to the constructor nothing is printed here as it would be just /* Compared to the constructor nothing is printed here as it would be just
repeating what was passed to the constructor */ repeating what was passed to the constructor */
Device out{NoCreate}; Device out{NoCreate};
out._handle = handle; out._handle = handle;
out._flags = flags; out._flags = flags;
out.initializeExtensions(enabledExtensions); out.initializeExtensions(enabledExtensions);
out.initialize(instance, version); out.initialize(instance, version, enabledFeatures);
return out; return out;
} }
Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const std::initializer_list<Containers::StringView> enabledExtensions, const HandleFlags flags) { Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const std::initializer_list<Containers::StringView> enabledExtensions, const DeviceFeatures& enabledFeatures, const HandleFlags flags) {
return wrap(instance, handle, version, Containers::arrayView(enabledExtensions), 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)} {} 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) for(std::size_t i = 0, max = info->enabledExtensionCount; i != max; ++i)
Debug{} << " " << info->ppEnabledExtensionNames[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)); MAGNUM_VK_INTERNAL_ASSERT_SUCCESS(instance->CreateDevice(info._physicalDevice, info, nullptr, &_handle));
initializeExtensions<const char*>({info->ppEnabledExtensionNames, info->enabledExtensionCount}); initializeExtensions<const char*>({info->ppEnabledExtensionNames, info->enabledExtensionCount});
initialize(instance, version); initialize(instance, version, info._state->enabledFeatures);
/* Extension-dependent state is initialized, now we can retrieve the queues /* Extension-dependent state is initialized, now we can retrieve the queues
from the device and save them to the outputs specified in addQueues(). from the device and save them to the outputs specified in addQueues().
@ -467,9 +673,10 @@ template<class T> void Device::initializeExtensions(const Containers::ArrayView<
} }
} }
void Device::initialize(Instance& instance, const Version version) { void Device::initialize(Instance& instance, const Version version, const DeviceFeatures& enabledFeatures) {
/* Init version, function pointers */ /* Init version, features, function pointers */
_version = version; _version = version;
_enabledFeatures = enabledFeatures;
flextVkInitDevice(_handle, &_functionPointers, instance->GetDeviceProcAddr); flextVkInitDevice(_handle, &_functionPointers, instance->GetDeviceProcAddr);
/* Set up extension-dependent functionality */ /* Set up extension-dependent functionality */

49
src/Magnum/Vk/Device.h

@ -31,6 +31,7 @@
*/ */
#include <cstddef> #include <cstddef>
#include <Corrade/Containers/BigEnumSet.h>
#include <Corrade/Containers/Pointer.h> #include <Corrade/Containers/Pointer.h>
#include "Magnum/Tags.h" #include "Magnum/Tags.h"
@ -90,8 +91,24 @@ checked with @ref isExtensionEnabled().
@snippet MagnumVk.cpp Device-creation-extensions @snippet MagnumVk.cpp Device-creation-extensions
Usually you'll be first checking for extension availability instead, which is In addition to extensions, you'll be usually enabling features as well. These
again accessible through the @ref DeviceProperties instance: 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 @snippet MagnumVk.cpp Device-creation-check-supported
@ -154,14 +171,18 @@ class MAGNUM_VK_EXPORT Device {
* device * device
* @param enabledExtensions Extensions that are assumed to be enabled * @param enabledExtensions Extensions that are assumed to be enabled
* on the device * on the device
* @param enabledFeatures Features that are assumed to be enabled on
* the device
* @param flags Handle flags * @param flags Handle flags
* *
* The @p handle is expected to be originating from @p instance. The * The @p handle is expected to be originating from @p instance. The
* @p version and @p enabledExtensions parameters populate internal * @p version, @p enabledExtensions and @p enabledFeatures parameters
* info about supported version and extensions and will be reflected in * populate internal info about supported version, enabled extensions
* @ref isVersionSupported() and @ref isExtensionEnabled(), among other * and enabled features and will be reflected in
* things. If @p enabledExtensions is empty, the device will behave as * @ref isVersionSupported(), @ref isExtensionEnabled() and
* if no extensions were enabled. * @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 * Note that this function retrieves all device-specific Vulkan
* function pointers, which is a relatively costly operation. It's thus * function pointers, which is a relatively costly operation. It's thus
@ -173,10 +194,10 @@ class MAGNUM_VK_EXPORT Device {
* behavior. * behavior.
* @see @ref release() * @see @ref release()
*/ */
static Device wrap(Instance& instance, VkDevice handle, Version version, Containers::ArrayView<const Containers::StringView> enabledExtensions, HandleFlags flags = {}); static Device wrap(Instance& instance, VkDevice handle, Version version, Containers::ArrayView<const Containers::StringView> enabledExtensions, const DeviceFeatures& enabledFeatures, HandleFlags flags = {});
/** @overload */ /** @overload */
static Device wrap(Instance& instance, VkDevice handle, Version version, std::initializer_list<Containers::StringView> enabledExtensions, HandleFlags flags = {}); static Device wrap(Instance& instance, VkDevice handle, Version version, std::initializer_list<Containers::StringView> enabledExtensions, const DeviceFeatures& enabledFeatures, HandleFlags flags = {});
/** /**
* @brief Constructor * @brief Constructor
@ -296,6 +317,13 @@ class MAGNUM_VK_EXPORT Device {
/** @overload */ /** @overload */
bool isExtensionEnabled(const Extension& extension) const; 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 * @brief Device-specific Vulkan function pointers
* *
@ -345,7 +373,7 @@ class MAGNUM_VK_EXPORT Device {
explicit Device(Instance& isntance, const DeviceCreateInfo&, DeviceProperties&&); explicit Device(Instance& isntance, const DeviceCreateInfo&, DeviceProperties&&);
template<class T> MAGNUM_VK_LOCAL void initializeExtensions(Containers::ArrayView<const T> enabledExtensions); template<class T> MAGNUM_VK_LOCAL void initializeExtensions(Containers::ArrayView<const T> 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 getQueueImplementationDefault(Device& self, const VkDeviceQueueInfo2& info, VkQueue& queue);
MAGNUM_VK_LOCAL static void getQueueImplementation11(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; HandleFlags _flags;
Version _version; Version _version;
Math::BoolVector<Implementation::ExtensionCount> _enabledExtensions; Math::BoolVector<Implementation::ExtensionCount> _enabledExtensions;
DeviceFeatures _enabledFeatures;
Containers::Pointer<DeviceProperties> _properties; Containers::Pointer<DeviceProperties> _properties;
Containers::Pointer<Implementation::DeviceState> _state; Containers::Pointer<Implementation::DeviceState> _state;

59
src/Magnum/Vk/DeviceCreateInfo.h

@ -200,6 +200,65 @@ class MAGNUM_VK_EXPORT DeviceCreateInfo {
return std::move(*this); 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 * @brief Add queues
* @param[in] family Family index, smaller than * @param[in] family Family index, smaller than

15
src/Magnum/Vk/DeviceProperties.cpp

@ -259,6 +259,21 @@ template<class E> bool DeviceProperties::isOrVersionSupportedInternal() {
return extensionPropertiesInternal().isSupported<E>(); return extensionPropertiesInternal().isSupported<E>();
} }
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() { const DeviceFeatures& DeviceProperties::features() {
if(!_state) _state.emplace(*_instance, _handle); if(!_state) _state.emplace(*_instance, _handle);

6
src/Magnum/Vk/DeviceProperties.h

@ -482,7 +482,8 @@ class MAGNUM_VK_EXPORT DeviceProperties {
* supported by the device, the `pNext` chain contains * supported by the device, the `pNext` chain contains
* @type_vk_keyword{PhysicalDeviceIndexTypeUint8FeaturesEXT} * @type_vk_keyword{PhysicalDeviceIndexTypeUint8FeaturesEXT}
* *
* @see @fn_vk_keyword{GetPhysicalDeviceFeatures2}, * @see @ref Device::enabledFeatures(),
* @fn_vk_keyword{GetPhysicalDeviceFeatures2},
* @fn_vk_keyword{GetPhysicalDeviceFeatures}, * @fn_vk_keyword{GetPhysicalDeviceFeatures},
* @type_vk_keyword{PhysicalDeviceFeatures2}, * @type_vk_keyword{PhysicalDeviceFeatures2},
* @type_vk_keyword{PhysicalDeviceFeatures} * @type_vk_keyword{PhysicalDeviceFeatures}
@ -715,6 +716,9 @@ class MAGNUM_VK_EXPORT DeviceProperties {
accidents with incorrectly specified extension core version. */ accidents with incorrectly specified extension core version. */
template<class E> MAGNUM_VK_LOCAL bool isOrVersionSupportedInternal(); template<class E> 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 getPropertiesImplementationDefault(DeviceProperties& self, VkPhysicalDeviceProperties2& properties);
MAGNUM_VK_LOCAL static void getPropertiesImplementationKHR(DeviceProperties& self, VkPhysicalDeviceProperties2& properties); MAGNUM_VK_LOCAL static void getPropertiesImplementationKHR(DeviceProperties& self, VkPhysicalDeviceProperties2& properties);
MAGNUM_VK_LOCAL static void getPropertiesImplementation11(DeviceProperties& self, VkPhysicalDeviceProperties2& properties); MAGNUM_VK_LOCAL static void getPropertiesImplementation11(DeviceProperties& self, VkPhysicalDeviceProperties2& properties);

5
src/Magnum/Vk/Implementation/DeviceFeatures.h

@ -31,7 +31,10 @@ namespace Magnum { namespace Vk { namespace Implementation {
/* Everything except the top-level VkPhysicalDeviceFeatures2 structure, which /* Everything except the top-level VkPhysicalDeviceFeatures2 structure, which
is treated differently when querying and enabling the features. Used by 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 { struct DeviceFeatures {
VkPhysicalDeviceProtectedMemoryFeatures protectedMemory; VkPhysicalDeviceProtectedMemoryFeatures protectedMemory;
VkPhysicalDeviceMultiviewFeatures multiview; VkPhysicalDeviceMultiviewFeatures multiview;

286
src/Magnum/Vk/Test/DeviceVkTest.cpp

@ -31,6 +31,7 @@
#include <Corrade/Utility/FormatStl.h> #include <Corrade/Utility/FormatStl.h>
#include "Magnum/Vk/DeviceCreateInfo.h" #include "Magnum/Vk/DeviceCreateInfo.h"
#include "Magnum/Vk/DeviceFeatures.h"
#include "Magnum/Vk/DeviceProperties.h" #include "Magnum/Vk/DeviceProperties.h"
#include "Magnum/Vk/Extensions.h" #include "Magnum/Vk/Extensions.h"
#include "Magnum/Vk/ExtensionProperties.h" #include "Magnum/Vk/ExtensionProperties.h"
@ -53,6 +54,11 @@ struct DeviceVkTest: VulkanTester {
void createInfoConstructNoImplicitExtensions(); void createInfoConstructNoImplicitExtensions();
void createInfoExtensions(); void createInfoExtensions();
void createInfoExtensionsCopiedStrings(); void createInfoExtensionsCopiedStrings();
void createInfoFeatures();
void createInfoFeaturesReplaceExternal();
void createInfoFeaturesReplacePrevious();
void createInfoFeaturesEnableAllResetAll();
void createInfoFeaturesNothingInCoreFeatures();
void createInfoNoQueuePriorities(); void createInfoNoQueuePriorities();
void createInfoWrongQueueOutputCount(); void createInfoWrongQueueOutputCount();
void createInfoConstructCopy(); void createInfoConstructCopy();
@ -62,6 +68,8 @@ struct DeviceVkTest: VulkanTester {
void construct(); void construct();
void constructQueueFromFlags(); void constructQueueFromFlags();
void constructExtensions(); void constructExtensions();
void constructFeatures();
void constructFeaturesFromExtensions();
void constructDeviceCreateInfoConstReference(); void constructDeviceCreateInfoConstReference();
void constructTransferDeviceProperties(); void constructTransferDeviceProperties();
void constructExtensionsCommandLineDisable(); void constructExtensionsCommandLineDisable();
@ -70,6 +78,8 @@ struct DeviceVkTest: VulkanTester {
void constructRawQueue(); void constructRawQueue();
void constructMove(); void constructMove();
void constructUnknownExtension(); void constructUnknownExtension();
void constructFeatureNotSupported();
void constructFeatureWithoutExtension();
void constructNoQueue(); void constructNoQueue();
void wrap(); void wrap();
@ -137,6 +147,11 @@ DeviceVkTest::DeviceVkTest(): VulkanTester{NoCreate} {
&DeviceVkTest::createInfoConstructNoImplicitExtensions, &DeviceVkTest::createInfoConstructNoImplicitExtensions,
&DeviceVkTest::createInfoExtensions, &DeviceVkTest::createInfoExtensions,
&DeviceVkTest::createInfoExtensionsCopiedStrings, &DeviceVkTest::createInfoExtensionsCopiedStrings,
&DeviceVkTest::createInfoFeatures,
&DeviceVkTest::createInfoFeaturesReplaceExternal,
&DeviceVkTest::createInfoFeaturesReplacePrevious,
&DeviceVkTest::createInfoFeaturesEnableAllResetAll,
&DeviceVkTest::createInfoFeaturesNothingInCoreFeatures,
&DeviceVkTest::createInfoNoQueuePriorities, &DeviceVkTest::createInfoNoQueuePriorities,
&DeviceVkTest::createInfoWrongQueueOutputCount, &DeviceVkTest::createInfoWrongQueueOutputCount,
&DeviceVkTest::createInfoConstructCopy, &DeviceVkTest::createInfoConstructCopy,
@ -146,6 +161,8 @@ DeviceVkTest::DeviceVkTest(): VulkanTester{NoCreate} {
&DeviceVkTest::construct, &DeviceVkTest::construct,
&DeviceVkTest::constructQueueFromFlags, &DeviceVkTest::constructQueueFromFlags,
&DeviceVkTest::constructExtensions, &DeviceVkTest::constructExtensions,
&DeviceVkTest::constructFeatures,
&DeviceVkTest::constructFeaturesFromExtensions,
&DeviceVkTest::constructDeviceCreateInfoConstReference, &DeviceVkTest::constructDeviceCreateInfoConstReference,
&DeviceVkTest::constructTransferDeviceProperties}); &DeviceVkTest::constructTransferDeviceProperties});
@ -158,6 +175,8 @@ DeviceVkTest::DeviceVkTest(): VulkanTester{NoCreate} {
&DeviceVkTest::constructMove, &DeviceVkTest::constructMove,
&DeviceVkTest::constructUnknownExtension, &DeviceVkTest::constructUnknownExtension,
&DeviceVkTest::constructFeatureNotSupported,
&DeviceVkTest::constructFeatureWithoutExtension,
&DeviceVkTest::constructNoQueue, &DeviceVkTest::constructNoQueue,
&DeviceVkTest::wrap, &DeviceVkTest::wrap,
@ -171,6 +190,7 @@ void DeviceVkTest::createInfoConstruct() {
CORRADE_VERIFY(info->sType); CORRADE_VERIFY(info->sType);
CORRADE_VERIFY(!info->pNext); CORRADE_VERIFY(!info->pNext);
/* Extensions might or might not be enabled */ /* Extensions might or might not be enabled */
CORRADE_VERIFY(!info->pEnabledFeatures);
} }
void DeviceVkTest::createInfoConstructNoImplicitExtensions() { void DeviceVkTest::createInfoConstructNoImplicitExtensions() {
@ -180,6 +200,7 @@ void DeviceVkTest::createInfoConstructNoImplicitExtensions() {
/* No extensions enabled as we explicitly disabled that */ /* No extensions enabled as we explicitly disabled that */
CORRADE_VERIFY(!info->ppEnabledExtensionNames); CORRADE_VERIFY(!info->ppEnabledExtensionNames);
CORRADE_COMPARE(info->enabledExtensionCount, 0); CORRADE_COMPARE(info->enabledExtensionCount, 0);
CORRADE_VERIFY(!info->pEnabledFeatures);
} }
void DeviceVkTest::createInfoExtensions() { void DeviceVkTest::createInfoExtensions() {
@ -229,6 +250,186 @@ void DeviceVkTest::createInfoExtensionsCopiedStrings() {
CORRADE_VERIFY(info->ppEnabledExtensionNames[1] != localButNullTerminated.data()); 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<Extensions::KHR::get_physical_device_properties2>()) {
CORRADE_VERIFY(!info->pEnabledFeatures);
CORRADE_VERIFY(info->pNext);
const auto& features2 = *static_cast<const VkPhysicalDeviceFeatures2*>(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<const VkPhysicalDeviceSamplerYcbcrConversionFeatures*>(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<Extensions::KHR::get_physical_device_properties2>()) {
CORRADE_VERIFY(!info->pEnabledFeatures);
CORRADE_VERIFY(info->pNext);
const auto& imagelessFramebufferFeatures = *static_cast<const VkPhysicalDeviceImagelessFramebufferFeatures*>(info->pNext);
CORRADE_COMPARE(imagelessFramebufferFeatures.sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES);
CORRADE_VERIFY(imagelessFramebufferFeatures.imagelessFramebuffer);
const auto& samplerYcbcrConversionFeatures = *static_cast<const VkPhysicalDeviceSamplerYcbcrConversionFeatures*>(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<Extensions::KHR::get_physical_device_properties2>()) {
CORRADE_VERIFY(!info->pEnabledFeatures);
CORRADE_VERIFY(info->pNext);
const auto& features2 = *static_cast<const VkPhysicalDeviceFeatures2*>(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<Extensions::KHR::get_physical_device_properties2>()) {
CORRADE_VERIFY(!info->pEnabledFeatures);
CORRADE_VERIFY(info->pNext);
const auto& features2 = *static_cast<const VkPhysicalDeviceFeatures2*>(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<Extensions::KHR::get_physical_device_properties2>()) {
CORRADE_VERIFY(!info->pEnabledFeatures);
CORRADE_VERIFY(info->pNext);
const auto& imagelessFramebufferFeatures = *static_cast<const VkPhysicalDeviceImagelessFramebufferFeatures*>(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<Extensions::KHR::get_physical_device_properties2>())
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() { void DeviceVkTest::createInfoNoQueuePriorities() {
#ifdef CORRADE_NO_ASSERT #ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
@ -417,6 +618,40 @@ void DeviceVkTest::constructExtensions() {
CORRADE_VERIFY(device->TrimCommandPoolKHR); 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<Extensions::KHR::sampler_ycbcr_conversion>())
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<Extensions::KHR::sampler_ycbcr_conversion>()
/* 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() { void DeviceVkTest::constructDeviceCreateInfoConstReference() {
Queue queue{NoCreate}; Queue queue{NoCreate};
DeviceProperties deviceProperties = pickDevice(instance()); DeviceProperties deviceProperties = pickDevice(instance());
@ -699,6 +934,45 @@ void DeviceVkTest::constructUnknownExtension() {
CORRADE_COMPARE(out.str(), "TODO"); 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<Extensions::KHR::get_physical_device_properties2>())
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<const VkBaseInStructure*>(info->pNext)->sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TEXTURE_COMPRESSION_ASTC_HDR_FEATURES_EXT);
CORRADE_VERIFY(static_cast<const VkPhysicalDeviceTextureCompressionASTCHDRFeaturesEXT*>(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() { void DeviceVkTest::constructNoQueue() {
#ifdef CORRADE_NO_ASSERT #ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
@ -743,7 +1017,10 @@ void DeviceVkTest::wrap() {
.addEnabledExtensions< .addEnabledExtensions<
Extensions::EXT::debug_marker, Extensions::EXT::debug_marker,
Extensions::KHR::maintenance1 Extensions::KHR::maintenance1
>(), >()
/* RobustBufferAccess is guaranteed to be supported always, no need
to check anything */
.setEnabledFeatures(DeviceFeature::RobustBufferAccess),
nullptr, &device)), Result::Success); nullptr, &device)), Result::Success);
CORRADE_VERIFY(device); CORRADE_VERIFY(device);
/* Populating the queue handle is done only from Device itself, so it won't /* 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 */ /* Wrapping should load the basic function pointers */
auto wrapped = Device::wrap(instance2, device, Version::Vk11, { auto wrapped = Device::wrap(instance2, device, Version::Vk11, {
Extensions::EXT::debug_marker::string() Extensions::EXT::debug_marker::string()
}, HandleFlag::DestroyOnDestruction); }, DeviceFeature::RobustBufferAccess, HandleFlag::DestroyOnDestruction);
CORRADE_VERIFY(wrapped->DestroyDevice); CORRADE_VERIFY(wrapped->DestroyDevice);
/* Specified version should be reported as supported but higher not /* Specified version should be reported as supported but higher not
@ -772,12 +1049,15 @@ void DeviceVkTest::wrap() {
CORRADE_VERIFY(!wrapped.isExtensionEnabled<Extensions::KHR::maintenance1>()); CORRADE_VERIFY(!wrapped.isExtensionEnabled<Extensions::KHR::maintenance1>());
CORRADE_VERIFY(wrapped->TrimCommandPoolKHR); CORRADE_VERIFY(wrapped->TrimCommandPoolKHR);
/* Listed features should be reported as enabled */
CORRADE_COMPARE(wrapped.enabledFeatures(), DeviceFeature::RobustBufferAccess);
/* Releasing won't destroy anything ... */ /* Releasing won't destroy anything ... */
CORRADE_COMPARE(wrapped.release(), device); CORRADE_COMPARE(wrapped.release(), device);
} }
/* ...so we can wrap it again, non-owned, and then destroy it manually */ /* ...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); CORRADE_VERIFY(wrapped->DestroyDevice);
wrapped->DestroyDevice(device, nullptr); wrapped->DestroyDevice(device, nullptr);
} }

Loading…
Cancel
Save