From 374f8167629eaa64933df0e5b1f857db4291d1a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 11 Jul 2020 00:37:05 +0200 Subject: [PATCH] Vk: device creation. --- doc/snippets/MagnumVk.cpp | 33 ++ doc/vulkan-mapping.dox | 13 +- src/Magnum/Vk/CMakeLists.txt | 3 + src/Magnum/Vk/Device.cpp | 335 ++++++++++++ src/Magnum/Vk/Device.h | 351 +++++++++++- src/Magnum/Vk/DeviceProperties.h | 5 +- src/Magnum/Vk/Implementation/Arguments.cpp | 4 +- src/Magnum/Vk/Implementation/DeviceState.cpp | 32 ++ src/Magnum/Vk/Implementation/DeviceState.h | 40 ++ src/Magnum/Vk/Instance.h | 2 + src/Magnum/Vk/Test/CMakeLists.txt | 2 + src/Magnum/Vk/Test/DeviceTest.cpp | 90 +++ src/Magnum/Vk/Test/DeviceVkTest.cpp | 546 +++++++++++++++++++ src/Magnum/Vk/Vk.h | 2 + 14 files changed, 1452 insertions(+), 6 deletions(-) create mode 100644 src/Magnum/Vk/Device.cpp create mode 100644 src/Magnum/Vk/Implementation/DeviceState.cpp create mode 100644 src/Magnum/Vk/Implementation/DeviceState.h create mode 100644 src/Magnum/Vk/Test/DeviceTest.cpp create mode 100644 src/Magnum/Vk/Test/DeviceVkTest.cpp diff --git a/doc/snippets/MagnumVk.cpp b/doc/snippets/MagnumVk.cpp index 7f446cb49..af0efd329 100644 --- a/doc/snippets/MagnumVk.cpp +++ b/doc/snippets/MagnumVk.cpp @@ -27,6 +27,7 @@ #include "Magnum/Magnum.h" #include "Magnum/Math/Color.h" +#include "Magnum/Vk/Device.h" #include "Magnum/Vk/Extensions.h" #include "Magnum/Vk/Instance.h" #include "Magnum/Vk/Integration.h" @@ -34,7 +35,39 @@ using namespace Magnum; +#define DOXYGEN_IGNORE(...) __VA_ARGS__ + int main() { +{ +Vk::Device device{NoCreate}; +/* [Device-isExtensionEnabled] */ +if(device.isExtensionEnabled()) { + // keep mesh indices 8bit +} else { + // convert them to 16bit +} +/* [Device-isExtensionEnabled] */ +} + +{ +Vk::Instance instance; +/* Header included again inside a function, but it's fine as the guards will + make it empty */ +/* [Device-global-function-pointers] */ +#include + +// … + +Vk::Device device{DOXYGEN_IGNORE(instance, Vk::DeviceCreateInfo{instance})}; +device.populateGlobalFunctionPointers(); + +VkCommandPool commandPool; +VkCommandPoolCreateInfo info{}; +// … +vkCreateCommandPool(device, &info, nullptr, &commandPool); +/* [Device-global-function-pointers] */ +} + { Vk::Instance instance; /* [Instance-isExtensionEnabled] */ diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index 7013bc575..eaea4a38d 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -130,7 +130,7 @@ Vulkan function | Matching API @fn_vk{CreateDescriptorPool}, \n @fn_vk{DestroyDescriptorPool} | | @fn_vk{CreateDescriptorSetLayout}, \n @fn_vk{DestroyDescriptorSetLayout} | | @fn_vk{CreateDescriptorUpdateTemplate} @m_class{m-label m-flat m-success} **KHR, 1.1**, \n @fn_vk{DestroyDescriptorUpdateTemplate} @m_class{m-label m-flat m-success} **KHR, 1.1** | | -@fn_vk{CreateDevice}, \n @fn_vk{DestroyDevice} | | +@fn_vk{CreateDevice}, \n @fn_vk{DestroyDevice} | @ref Device constructor and destructor @fn_vk{CreateEvent}, \n @fn_vk{DestroyEvent} | | @fn_vk{CreateFence}, \n @fn_vk{DestroyFence} | | @fn_vk{CreateFramebuffer}, \n @fn_vk{DestroyFramebuffer} | | @@ -193,7 +193,7 @@ Vulkan function | Matching API @fn_vk{GetDescriptorSetLayoutSupport} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{GetDeviceGroupPeerMemoryFeatures} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{GetDeviceMemoryCommitment} | | -@fn_vk{GetDeviceProcAddr} | | +@fn_vk{GetDeviceProcAddr} | @ref Device constructor @fn_vk{GetDeviceQueue}, \n @fn_vk{GetDeviceQueue2} @m_class{m-label m-flat m-success} **1.1** | | @fn_vk{GetEventStatus} | | @fn_vk{GetFenceStatus} | | @@ -304,6 +304,15 @@ Vulkan structure | Matching API --------------------------------------- | ------------ @type_vk{ApplicationInfo} | @ref InstanceCreateInfo +@subsection vulkan-mapping-structures-d D + +@m_class{m-fullwidth} + +Vulkan structure | Matching API +--------------------------------------- | ------------ +@type_vk{DeviceCreateInfo} | @ref DeviceCreateInfo +@type_vk{DeviceQueueCreateInfo} | not exposed, but you can pass a custom instance to @ref DeviceCreateInfo::addQueues(const VkDeviceQueueCreateInfo&) + @subsection vulkan-mapping-structures-i I @m_class{m-fullwidth} diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index de57eae0c..2ea9e44e4 100644 --- a/src/Magnum/Vk/CMakeLists.txt +++ b/src/Magnum/Vk/CMakeLists.txt @@ -34,9 +34,11 @@ set(MagnumVk_SRCS Version.cpp Implementation/Arguments.cpp + Implementation/DeviceState.cpp Implementation/InstanceState.cpp) set(MagnumVk_GracefulAssert_SRCS + Device.cpp DeviceProperties.cpp Enums.cpp ExtensionProperties.cpp @@ -62,6 +64,7 @@ set(MagnumVk_HEADERS set(MagnumVk_PRIVATE_HEADERS Implementation/Arguments.h + Implementation/DeviceState.h Implementation/InstanceState.h Implementation/compressedPixelFormatMapping.hpp diff --git a/src/Magnum/Vk/Device.cpp b/src/Magnum/Vk/Device.cpp new file mode 100644 index 000000000..a98411139 --- /dev/null +++ b/src/Magnum/Vk/Device.cpp @@ -0,0 +1,335 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Device.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Magnum/Math/Functions.h" +#include "Magnum/Vk/Handle.h" +#include "Magnum/Vk/Instance.h" +#include "Magnum/Vk/DeviceProperties.h" +#include "Magnum/Vk/Extensions.h" +#include "Magnum/Vk/ExtensionProperties.h" +#include "Magnum/Vk/Result.h" +#include "Magnum/Vk/Version.h" +#include "Magnum/Vk/Implementation/Arguments.h" +#include "Magnum/Vk/Implementation/InstanceState.h" +#include "Magnum/Vk/Implementation/DeviceState.h" +#include "MagnumExternal/Vulkan/flextVkGlobal.h" + +namespace Magnum { namespace Vk { + +struct DeviceCreateInfo::State { + Containers::Array ownedStrings; + Containers::Array extensions; + + Containers::String disabledExtensionsStorage; + Containers::Array disabledExtensions; + Containers::Array queues; + Containers::StaticArray<32, Float> queuePriorities; + + std::size_t nextQueuePriority = 0; + bool quietLog = false; + Version version = Version::None; +}; + +DeviceCreateInfo::DeviceCreateInfo(DeviceProperties& deviceProperties, const ExtensionProperties* extensionProperties, const Flags flags): _physicalDevice{deviceProperties}, _info{}, _state{Containers::InPlaceInit} { + Utility::Arguments args = Implementation::arguments(); + args.parse(deviceProperties._instance->state().argc, deviceProperties._instance->state().argv); + + if(args.value("log") == "quiet") + _state->quietLog = true; + + _info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + _info.flags = VkDeviceCreateFlags(flags & ~Flag::NoImplicitExtensions); + + /* Take the minimum of instance and device version. Instance version being + smaller than a device version happens mainly if there's a forced Vulkan + version via --magnum-vulkan version, which will be later used to cap available features. */ + _state->version = Version(Math::min(UnsignedInt(deviceProperties._instance->version()), UnsignedInt(deviceProperties.apiVersion()))); + + /* If there are any disabled extensions, sort them and save for later -- + we'll use them to filter the ones added by the app */ + Containers::String disabledExtensions = args.value("disable-extensions"); + if(!disabledExtensions.isEmpty()) { + _state->disabledExtensionsStorage = std::move(disabledExtensions); + _state->disabledExtensions = Containers::StringView{_state->disabledExtensionsStorage}.splitWithoutEmptyParts(); + std::sort(_state->disabledExtensions.begin(), _state->disabledExtensions.end()); + } + + /* Add all extensions enabled on command-line. The blacklist is applied on + those as well. */ + /** @todo use a generator split() so we can avoid the growing allocation + of the output array */ + /** @todo unfortunately even though the split and value retrieval is mostly + allocation-free, the strings will be turned into owning copies because + none of them is null-terminated or global -- could be a better idea to + just grow one giant string internally (once we have growable strings) */ + addEnabledExtensions(args.value("enable-extensions").splitWithoutEmptyParts()); + + /* Enable implicit extensions unless that's forbidden */ + /** @todo move this somewhere else as this will grow significantly? */ + if(!(flags & Flag::NoImplicitExtensions)) { + /* Fetch searchable extension properties if not already */ + Containers::Optional extensionPropertiesStorage; + if(!extensionProperties) { + /** @todo i'd like to know which layers are enabled so i can list + the exts from those .. but how? */ + extensionPropertiesStorage = deviceProperties.enumerateExtensionProperties(); + extensionProperties = &*extensionPropertiesStorage; + } + + /* No extensions at the moment */ + } +} + +DeviceCreateInfo::DeviceCreateInfo(Instance& instance, const Flags flags): DeviceCreateInfo{pickDevice(instance), flags} {} + +DeviceCreateInfo::DeviceCreateInfo(NoInitT) noexcept {} + +DeviceCreateInfo::DeviceCreateInfo(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo& info): _physicalDevice{physicalDevice}, _info{info} {} + +DeviceCreateInfo::~DeviceCreateInfo() = default; + +DeviceCreateInfo& DeviceCreateInfo::addEnabledExtensions(const Containers::ArrayView extensions) { + if(extensions.empty()) return *this; + /* This can happen in case we used the NoInit or VkDeviceCreateInfo + constructor */ + if(!_state) _state.emplace(); + + /* Add null-terminated strings to the extension array */ + arrayReserve(_state->extensions, _state->extensions.size() + extensions.size()); + for(const Containers::StringView extension: extensions) { + /* If the extension is blacklisted, skip it */ + if(std::binary_search(_state->disabledExtensions.begin(), _state->disabledExtensions.end(), extension)) continue; + + /* Keep an owned *allocated* copy of the string if it's not global or + null-terminated -- ideally, if people use string view literals, + those will be, so this won't allocate. Allocated so the pointers + don't get invalidated when the array gets reallocated. */ + const char* data; + if(!(extension.flags() >= (Containers::StringViewFlag::NullTerminated|Containers::StringViewFlag::Global))) + data = arrayAppend(_state->ownedStrings, Containers::InPlaceInit, + Containers::AllocatedInit, extension).data(); + else data = extension.data(); + + arrayAppend(_state->extensions, data); + } + + /* Update the extension count, re-route the pointer to the layers array in + case it got reallocated */ + _info.enabledExtensionCount = _state->extensions.size(); + _info.ppEnabledExtensionNames = _state->extensions.data(); + return *this; +} + +DeviceCreateInfo& DeviceCreateInfo::addEnabledExtensions(const std::initializer_list extensions) { + return addEnabledExtensions(Containers::arrayView(extensions)); +} + +DeviceCreateInfo& DeviceCreateInfo::addEnabledExtensions(const Containers::ArrayView extensions) { + if(extensions.empty()) return *this; + /* This can happen in case we used the NoInit or VkDeviceCreateInfo + constructor */ + if(!_state) _state.emplace(); + + arrayReserve(_state->extensions, _state->extensions.size() + extensions.size()); + for(const Extension& extension: extensions) { + /* If the extension is blacklisted, skip it */ + if(std::binary_search(_state->disabledExtensions.begin(), _state->disabledExtensions.end(), extension.string())) continue; + + arrayAppend(_state->extensions, extension.string().data()); + } + + /* Update the extension count, re-route the pointer to the layers array in + case it got reallocated */ + _info.enabledExtensionCount = _state->extensions.size(); + _info.ppEnabledExtensionNames = _state->extensions.data(); + return *this; +} + +DeviceCreateInfo& DeviceCreateInfo::addEnabledExtensions(const std::initializer_list extensions) { + return addEnabledExtensions(Containers::arrayView(extensions)); +} + +DeviceCreateInfo& DeviceCreateInfo::addQueues(UnsignedInt family, Containers::ArrayView priorities) { + CORRADE_ASSERT(!priorities.empty(), "Vk::DeviceCreateInfo::addQueues(): at least one queue priority has to be specified", *this); + + /* This can happen in case we used the NoInit or VkDeviceCreateInfo + constructor */ + if(!_state) _state.emplace(); + + VkDeviceQueueCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + info.queueFamilyIndex = family; + info.queueCount = priorities.size(); + info.pQueuePriorities = _state->queuePriorities + _state->nextQueuePriority; + + /* Copy the passed queue priorities to an internal storage that never + reallocates. If this blows up, see the definition of queuePriorities for + details. We can't easily reallocate if this grows too big as all + pointers would need to be patched, so there's a static limit. */ + CORRADE_INTERNAL_ASSERT(_state->nextQueuePriority + priorities.size() <= _state->queuePriorities.size()); + Utility::copy(priorities, _state->queuePriorities.suffix(_state->nextQueuePriority).prefix(priorities.size())); + _state->nextQueuePriority += priorities.size(); + + return addQueues(info); +} + +DeviceCreateInfo& DeviceCreateInfo::addQueues(UnsignedInt family, std::initializer_list priorities) { + return addQueues(family, Containers::arrayView(priorities)); +} + +DeviceCreateInfo& DeviceCreateInfo::addQueues(const VkDeviceQueueCreateInfo& info) { + /* This can happen in case we used the NoInit or VkDeviceCreateInfo + constructor */ + if(!_state) _state.emplace(); + + /* Copy the info to an internal storage and re-route the pointer to it. + This handles a potential reallocation and also the case of replacing the + default queue on the first call to addQueues(). */ + arrayAppend(_state->queues, info); + _info.pQueueCreateInfos = _state->queues; + _info.queueCreateInfoCount = _state->queues.size(); + + return *this; +} + +Device Device::wrap(Instance& instance, const VkDevice handle, const Version version, const Containers::ArrayView enabledExtensions, 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); + 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(Instance& instance, const DeviceCreateInfo& info): + #ifdef CORRADE_GRACEFUL_ASSERT + _handle{}, /* Otherwise the destructor dies if we hit the queue assert */ + #endif + _flags{HandleFlag::DestroyOnDestruction} +{ + CORRADE_ASSERT(info._info.queueCreateInfoCount, + "Vk::Device: needs to be created with at least one queue", ); + + const Version version = info._state->version != Version::None ? info._state->version : DeviceProperties::wrap(instance, info._physicalDevice).apiVersion(); + + /* Print all enabled extensions if we're not told to be quiet */ + if(!info._state || !info._state->quietLog) { + Debug{} << "Device version:" << version; + + if(info->enabledExtensionCount) { + Debug{} << "Enabled device extensions:"; + for(std::size_t i = 0, max = info->enabledExtensionCount; i != max; ++i) + Debug{} << " " << info->ppEnabledExtensionNames[i]; + } + } + + MAGNUM_VK_INTERNAL_ASSERT_RESULT(instance->CreateDevice(info._physicalDevice, info, nullptr, &_handle)); + + initializeExtensions({info->ppEnabledExtensionNames, info->enabledExtensionCount}); + initialize(instance, version); +} + +Device::Device(NoCreateT): _handle{}, _functionPointers{} {} + +Device::Device(Device&& other) noexcept: _handle{other._handle}, _flags{other._flags}, _version{other._version}, _extensionStatus{other._extensionStatus}, _state{std::move(other._state)}, _functionPointers{other._functionPointers} { + other._handle = nullptr; + other._functionPointers = {}; +} + +Device::~Device() { + if(_handle && (_flags & HandleFlag::DestroyOnDestruction)) + _functionPointers.DestroyDevice(_handle, nullptr); +} + +Device& Device::operator=(Device&& other) noexcept { + using std::swap; + swap(other._handle, _handle); + swap(other._flags, _flags); + swap(other._version, _version); + swap(other._extensionStatus, _extensionStatus); + swap(other._state, _state); + swap(other._functionPointers, _functionPointers); + return *this; +} + +template void Device::initializeExtensions(const Containers::ArrayView enabledExtensions) { + /* Mark all known extensions as enabled */ + for(const T extension: enabledExtensions) { + for(Containers::ArrayView knownExtensions: { + Extension::extensions(Version::None), + /*Extension::extensions(Version::Vk10), is empty */ + Extension::extensions(Version::Vk11), + Extension::extensions(Version::Vk12) + }) { + auto found = std::lower_bound(knownExtensions.begin(), knownExtensions.end(), extension, [](const Extension& a, const T& b) { + return a.string() < static_cast(b); + }); + if(found->string() != extension) continue; + _extensionStatus.set(found->index(), true); + } + } +} + +void Device::initialize(Instance& instance, const Version version) { + /* Init version, function pointers */ + _version = version; + flextVkInitDevice(_handle, &_functionPointers, instance->GetDeviceProcAddr); + + /* Set up extension-dependent functionality */ + _state.emplace(*this); +} + +bool Device::isExtensionEnabled(const Extension& extension) const { + return _extensionStatus[extension.index()]; +} + +VkDevice Device::release() { + const VkDevice handle = _handle; + _handle = nullptr; + return handle; +} + +void Device::populateGlobalFunctionPointers() { + flextVkDevice = _functionPointers; +} + +}} diff --git a/src/Magnum/Vk/Device.h b/src/Magnum/Vk/Device.h index c707338d1..69a14742d 100644 --- a/src/Magnum/Vk/Device.h +++ b/src/Magnum/Vk/Device.h @@ -26,18 +26,367 @@ */ /** @file - * @brief Nothing, haha + * @brief Class @ref Magnum::Vk::DeviceCreateInfo, @ref Magnum::Vk::Device * @m_since_latest */ #include +#include + +#include "Magnum/Tags.h" +#include "Magnum/Math/BoolVector.h" +#include "Magnum/Vk/TypeTraits.h" +#include "Magnum/Vk/Vk.h" +#include "Magnum/Vk/Vulkan.h" +#include "Magnum/Vk/visibility.h" namespace Magnum { namespace Vk { namespace Implementation { enum: std::size_t { ExtensionCount = 72 }; + + struct DeviceState; } +/** +@brief Device creation info +@m_since_latest + +Wraps a @type_vk_keyword{DeviceCreateInfo}. +@see @ref Device +*/ +class MAGNUM_VK_EXPORT DeviceCreateInfo { + public: + /** + * @brief Device creation flag + * + * Wraps @type_vk_keyword{DeviceCreateFlagBits}. + * @see @ref Flags, @ref DeviceCreateInfo(DeviceProperties&, const ExtensionProperties*, Flags) + */ + enum class Flag: UnsignedInt { + /* Any magnum-specific flags added here have to be filtered out + when passing them to _info.flags in the constructor. Using the + highest bits in a hope to prevent conflicts with Vulkan instance + flags added in the future. */ + + /** + * Don't implicitly enable any extensions. + * + * By default, the engine enables various extensions such as + * @vk_extension{KHR,get_memory_requirements2} to provide a broader + * functionality. If you want to have a complete control over what + * gets enabled, set this flag. + */ + NoImplicitExtensions = 1u << 31 + }; + + /** + * @brief Device creation flags + * + * Type-safe wrapper for @type_vk_keyword{DeviceCreateFlags}. + * @see @ref DeviceCreateInfo(DeviceProperties&, const ExtensionProperties*, Flags) + */ + typedef Containers::EnumSet Flags; + + /** + * @brief Constructor + * @param deviceProperties A device to use + * @param extensionProperties Existing @ref ExtensionProperties + * instance for querying available Vulkan extensions. If + * @cpp nullptr @ce, a new instance may be created internally if + * needed. + * @param flags Device creation flags + * + * The following @type_vk{DeviceCreateInfo} fields are pre-filled in + * addition to `sType`: + * + * - `flags` + * + * You need to call at least @ref addQueues() for a valid setup. + */ + explicit DeviceCreateInfo(DeviceProperties& deviceProperties, const ExtensionProperties* extensionProperties, Flags flags = {}); + + /** @overload */ + explicit DeviceCreateInfo(DeviceProperties&& deviceProperties, const ExtensionProperties* extensionProperties, Flags flags = {}): DeviceCreateInfo{deviceProperties, extensionProperties, flags} {} + + /** @overload */ + explicit DeviceCreateInfo(DeviceProperties& deviceProperties, Flags flags = {}): DeviceCreateInfo{deviceProperties, nullptr, flags} {} + + /** @overload */ + explicit DeviceCreateInfo(DeviceProperties&& deviceProperties, Flags flags = {}): DeviceCreateInfo{std::move(deviceProperties), nullptr, flags} {} + + /** + * @brief Construct for an implicitly picked device + * + * Calls @ref DeviceCreateInfo(DeviceProperties&, const ExtensionProperties*, Flags) + * with a device picked from @p instance using @ref pickDevice(). + */ + explicit DeviceCreateInfo(Instance& instance, Flags flags = {}); + + /** + * @brief Construct without initializing the contents + * + * Note that not even the `sType` field is set --- the structure has to + * be fully initialized afterwards in order to be usable. + */ + explicit DeviceCreateInfo(NoInitT) noexcept; + + /** + * @brief Construct from existing data + * + * Copies the existing values verbatim, pointers are kept unchanged + * without taking over the ownership. Modifying the newly created + * instance will not modify the original data nor the pointed-to data. + */ + explicit DeviceCreateInfo(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo& info); + + ~DeviceCreateInfo(); + + /** + * @brief Add enabled device extensions + * @return Reference to self (for method chaining) + * + * All listed extensions are expected to be supported either globally + * or in at least one of the enabled layers, use + * @ref ExtensionProperties::isSupported() to check for their presence. + * + * The function makes copies of string views that are not owning or + * null-terminated, use the @link Containers::Literals::operator""_s() @endlink + * literal to prevent that where possible. + */ + DeviceCreateInfo& addEnabledExtensions(Containers::ArrayView extensions); + /** @overload */ + DeviceCreateInfo& addEnabledExtensions(std::initializer_list extension); + /** @overload */ + DeviceCreateInfo& addEnabledExtensions(Containers::ArrayView extensions); + /** @overload */ + DeviceCreateInfo& addEnabledExtensions(std::initializer_list extension); + /** @overload */ + template DeviceCreateInfo& addEnabledExtensions() { + static_assert(Implementation::IsExtension::value, "expected only Vulkan device extensions"); + return addEnabledExtensions({E{}...}); + } + + /** + * @brief Add queues + * @param family Family index, smaller than + * @ref DeviceProperties::queueFamilyCount() + * @param priorities Queue priorities. Size of the array implies how + * many queues to add and has to be at least one. + * @return Reference to self (for method chaining) + * + * At least one queue has to be added. + * @see @ref DeviceProperties::pickQueueFamily() + */ + DeviceCreateInfo& addQueues(UnsignedInt family, Containers::ArrayView priorities); + /** @overload */ + DeviceCreateInfo& addQueues(UnsignedInt family, std::initializer_list priorities); + + /** + * @brief Add queues using raw info + * @return Reference to self (for method chaining) + * + * Compared to @ref addQueues() this allows you to specify additional + * queue properties using the `pNext` chain. The info is uses as-is, + * with all pointers expected to stay in scope until device creation. + */ + DeviceCreateInfo& addQueues(const VkDeviceQueueCreateInfo& info); + + /** @brief Underlying @type_vk{DeviceCreateInfo} structure */ + VkDeviceCreateInfo& operator*() { return _info; } + /** @overload */ + const VkDeviceCreateInfo& operator*() const { return _info; } + /** @overload */ + VkDeviceCreateInfo* operator->() { return &_info; } + /** @overload */ + const VkDeviceCreateInfo* operator->() const { return &_info; } + /** @overload */ + operator const VkDeviceCreateInfo*() const { return &_info; } + + private: + friend Device; + + VkPhysicalDevice _physicalDevice; + VkDeviceCreateInfo _info; + struct State; + Containers::Pointer _state; +}; + +CORRADE_ENUMSET_OPERATORS(DeviceCreateInfo::Flags) + +/** +@brief Device +@m_since_latest + +Wraps a @type_vk_keyword{Device} and stores all device-specific function +pointers. +*/ +class MAGNUM_VK_EXPORT Device { + public: + /** + * @brief Wrap existing Vulkan handle + * @param instance Vulkan instance the device is created on + * @param handle The @type_vk{Device} handle + * @param version Vulkan version that's assumed to be used on the + * instance + * @param enabledExtensions Extensions that are assumed to be enabled + * on the instance + * @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. + * + * Unlike a device created using a constructor, the Vulkan device is by + * default not deleted on destruction, use @p flags for different + * behavior. + * @see @ref release() + */ + static Device wrap(Instance& instance, VkDevice handle, Version version, Containers::ArrayView enabledExtensions, HandleFlags flags = {}); + + /** @overload */ + static Device wrap(Instance& instance, VkDevice handle, Version version, std::initializer_list enabledExtensions, HandleFlags flags = {}); + + /** + * @brief Constructor + * @param instance Vulkan instance to create the device on + * @param info Device creation info + * + * @see @fn_vk_keyword{CreateDevice} + */ + explicit Device(Instance& instance, const DeviceCreateInfo& info); + + /** + * @brief Construct without creating the device + * + * The constructed instance is equivalent to moved-from state. Useful + * in cases where you will overwrite the instance later anyway. Move + * another object over it to make it useful. + */ + explicit Device(NoCreateT); + + /** @brief Copying is not allowed */ + Device(const Device&) = delete; + + /** @brief Move constructor */ + Device(Device&& other) noexcept; + + /** + * @brief Destructor + * + * Destroys associated @type_vk{Device} handle, unless the instance + * was created using @ref wrap() without @ref HandleFlag::DestroyOnDestruction + * specified. + * @see @fn_vk_keyword{DestroyDevice}, @ref release() + */ + ~Device(); + + /** @brief Copying is not allowed */ + Device& operator=(const Device&) = delete; + + /** @brief Move assignment */ + Device& operator=(Device&& other) noexcept; + + /** @brief Underlying @type_vk{Device} handle */ + VkDevice handle() { return _handle; } + /** @overload */ + operator VkDevice() { return _handle; } + + /** @brief Handle flags */ + HandleFlags handleFlags() const { return _flags; } + + /** @brief Device version */ + Version version() const { return _version; } + + /** @brief Whether given version is supported on the device */ + bool isVersionSupported(Version version) const { + return _version >= version; + } + + /** + * @brief Whether given extension is enabled + * + * Accepts device extensions from the @ref Extensions namespace, listed + * also in the @ref vulkan-support "Vulkan support tables". Search + * complexity is @f$ \mathcal{O}(1) @f$. Example usage: + * + * @snippet MagnumVk.cpp Device-isExtensionEnabled + * + * Note that this returns @cpp true @ce only if given extension is + * supported by the driver *and* it was enabled in + * @ref DeviceCreateInfo when creating the @ref Device. For querying + * extension support before creating a device use + * @ref ExtensionProperties::isSupported(). + */ + template bool isExtensionEnabled() const { + static_assert(Implementation::IsExtension::value, "expected a Vulkan device extension"); + return _extensionStatus[E::Index]; + } + + /** @overload */ + bool isExtensionEnabled(const Extension& extension) const; + + /** + * @brief Device-specific Vulkan function pointers + * + * Function pointers are implicitly stored per-device, use + * @ref populateGlobalFunctionPointers() to populate the global `vk*` + * functions. + */ + const FlextVkDevice& operator*() const { return _functionPointers; } + /** @overload */ + const FlextVkDevice* operator->() const { return &_functionPointers; } + + /** + * @brief Release the underlying Vulkan device + * + * Releases ownership of the Vulkan device and returns its handle so + * @fn_vk{DestroyDevice} is not called on destruction. The internal + * state is then equivalent to moved-from state. + * @see @ref wrap() + */ + VkDevice release(); + + /** + * @brief Populate global device-level function pointers to be used with third-party code + * + * Populates device-level global function pointers so third-party + * code is able to call global device-level `vk*` functions: + * + * @snippet MagnumVk.cpp Device-global-function-pointers + * + * Use @ref Instance::populateGlobalFunctionPointers() to populate + * instance-level global function pointers. + * @attention This operation is changing global state. You need to + * ensure that this function is not called simultaenously from + * multiple threads and code using those function points is + * calling them with the same device as the one returned by + * @ref handle(). + */ + void populateGlobalFunctionPointers(); + + #ifdef DOXYGEN_GENERATING_OUTPUT + private: + #endif + Implementation::DeviceState& state() { return *_state; } + + private: + template MAGNUM_VK_LOCAL void initializeExtensions(Containers::ArrayView enabledExtensions); + MAGNUM_VK_LOCAL void initialize(Instance& instance, Version version); + + VkDevice _handle; + HandleFlags _flags; + Version _version; + Math::BoolVector _extensionStatus; + Containers::Pointer _state; + + /* This member is bigger than you might think */ + FlextVkDevice _functionPointers; +}; + }} #endif diff --git a/src/Magnum/Vk/DeviceProperties.h b/src/Magnum/Vk/DeviceProperties.h index 4e94bbb34..9a2efd0b2 100644 --- a/src/Magnum/Vk/DeviceProperties.h +++ b/src/Magnum/Vk/DeviceProperties.h @@ -288,8 +288,8 @@ class MAGNUM_VK_EXPORT DeviceProperties { /** * @brief Pick a queue family satisfying given flags - * @return Queue family index for use in @ref queueFamilySize() and - * @ref queueFamilyFlags() + * @return Queue family index for use in @ref queueFamilySize(), + * @ref queueFamilyFlags() and @ref DeviceCreateInfo::addQueues() * * Queries queue family properties using @ref queueFamilyProperties() * and tries to find the first that contains all @p flags. If it is not @@ -313,6 +313,7 @@ class MAGNUM_VK_EXPORT DeviceProperties { #ifndef DOXYGEN_GENERATING_OUTPUT /* The DAMN THING lists this among friends, which is AN IMPLEMENTATION DETAIL */ + friend DeviceCreateInfo; friend MAGNUM_VK_EXPORT Containers::Array enumerateDevices(Instance&); #endif diff --git a/src/Magnum/Vk/Implementation/Arguments.cpp b/src/Magnum/Vk/Implementation/Arguments.cpp index a9fae5c73..9a62f80dc 100644 --- a/src/Magnum/Vk/Implementation/Arguments.cpp +++ b/src/Magnum/Vk/Implementation/Arguments.cpp @@ -32,9 +32,10 @@ namespace Magnum { namespace Vk { namespace Implementation { Utility::Arguments arguments() { Utility::Arguments args{"magnum"}; args.addOption("disable-layers").setHelp("disable-layers", "Vulkan layers to disable", "LIST") - .addOption("disable-extensions").setHelp("disable-extensions", "Vulkan extensions to disable", "LIST") + .addOption("disable-extensions").setHelp("disable-extensions", "Vulkan instance or device extensions to disable", "LIST") .addOption("enable-layers").setHelp("enable-layers", "Vulkan layers to enable in addition to the defaults and what the application requests", "LIST") .addOption("enable-instance-extensions").setHelp("enable-instance-extensions", "Vulkan instance extensions to enable in addition to the defaults and what the application requests", "LIST") + .addOption("enable-extensions").setHelp("enable-extensions", "Vulkan device extensions to enable in addition to the defaults and what the application requests", "LIST") .addOption("vulkan-version").setHelp("vulkan-version", "force Vulkan version", "X.Y") .addOption("log", "default").setHelp("log", "console logging", "default|quiet|verbose") .addOption("device").setHelp("device", "device to use", "ID|integrated|discrete|virtual|cpu") @@ -42,6 +43,7 @@ Utility::Arguments arguments() { .setFromEnvironment("disable-extensions") .setFromEnvironment("enable-layers") .setFromEnvironment("enable-instance-extensions") + .setFromEnvironment("enable-extensions") .setFromEnvironment("vulkan-version") .setFromEnvironment("log") .setFromEnvironment("device"); diff --git a/src/Magnum/Vk/Implementation/DeviceState.cpp b/src/Magnum/Vk/Implementation/DeviceState.cpp new file mode 100644 index 000000000..7475ec1bd --- /dev/null +++ b/src/Magnum/Vk/Implementation/DeviceState.cpp @@ -0,0 +1,32 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "DeviceState.h" + +namespace Magnum { namespace Vk { namespace Implementation { + +DeviceState::DeviceState(Device&) {} + +}}} diff --git a/src/Magnum/Vk/Implementation/DeviceState.h b/src/Magnum/Vk/Implementation/DeviceState.h new file mode 100644 index 000000000..1d09020b6 --- /dev/null +++ b/src/Magnum/Vk/Implementation/DeviceState.h @@ -0,0 +1,40 @@ +#ifndef Magnum_Vk_Implementation_DeviceState_h +#define Magnum_Vk_Implementation_DeviceState_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Magnum/Vk/Vk.h" +#include "Magnum/Vk/Vulkan.h" + +namespace Magnum { namespace Vk { namespace Implementation { + +struct DeviceState { + explicit DeviceState(Device& instance); +}; + +}}} + +#endif + diff --git a/src/Magnum/Vk/Instance.h b/src/Magnum/Vk/Instance.h index f04356a9c..5c4e520d8 100644 --- a/src/Magnum/Vk/Instance.h +++ b/src/Magnum/Vk/Instance.h @@ -364,6 +364,8 @@ class MAGNUM_VK_EXPORT Instance { * * @snippet MagnumVk.cpp Instance-global-function-pointers * + * Use @ref Device::populateGlobalFunctionPointers() to populate + * device-level global function pointers. * @attention This operation is changing global state. You need to * ensure that this function is not called simultaenously from * multiple threads and code using those function points is diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index 9ec60218b..15190d00f 100644 --- a/src/Magnum/Vk/Test/CMakeLists.txt +++ b/src/Magnum/Vk/Test/CMakeLists.txt @@ -24,6 +24,7 @@ # DEALINGS IN THE SOFTWARE. # +corrade_add_test(VkDeviceTest DeviceTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkDevicePropertiesTest DevicePropertiesTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkEnumsTest EnumsTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkExtensionsTest ExtensionsTest.cpp LIBRARIES MagnumVk) @@ -36,6 +37,7 @@ corrade_add_test(VkResultTest ResultTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkVersionTest VersionTest.cpp LIBRARIES MagnumVk) if(BUILD_VK_TESTS) + corrade_add_test(VkDeviceVkTest DeviceVkTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkDevicePropertiesVkTest DevicePropertiesVkTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkExtensionPropertiesVkTest ExtensionPropertiesVkTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkLayerPropertiesVkTest LayerPropertiesVkTest.cpp LIBRARIES MagnumVkTestLib) diff --git a/src/Magnum/Vk/Test/DeviceTest.cpp b/src/Magnum/Vk/Test/DeviceTest.cpp new file mode 100644 index 000000000..af372fa1b --- /dev/null +++ b/src/Magnum/Vk/Test/DeviceTest.cpp @@ -0,0 +1,90 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Vk/Device.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct DeviceTest: TestSuite::Tester { + explicit DeviceTest(); + + void createInfoConstructNoInit(); + void createInfoConstructFromVk(); + + void constructNoCreate(); + void constructCopy(); +}; + +DeviceTest::DeviceTest() { + addTests({&DeviceTest::createInfoConstructNoInit, + &DeviceTest::createInfoConstructFromVk, + + &DeviceTest::constructNoCreate, + &DeviceTest::constructCopy}); +} + +void DeviceTest::createInfoConstructNoInit() { + DeviceCreateInfo info{NoInit}; + info->sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + new(&info) DeviceCreateInfo{NoInit}; + CORRADE_COMPARE(info->sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void DeviceTest::createInfoConstructFromVk() { + VkDeviceCreateInfo vkInfo; + vkInfo.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + + DeviceCreateInfo info{VkPhysicalDevice{}, vkInfo}; + CORRADE_COMPARE(info->sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); +} + +void DeviceTest::constructNoCreate() { + { + Device device{NoCreate}; + CORRADE_VERIFY(!device.handle()); + /* Device function pointers should be null */ + CORRADE_VERIFY(!device->CreateBuffer); + } + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void DeviceTest::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::DeviceTest) diff --git a/src/Magnum/Vk/Test/DeviceVkTest.cpp b/src/Magnum/Vk/Test/DeviceVkTest.cpp new file mode 100644 index 000000000..f1c95f052 --- /dev/null +++ b/src/Magnum/Vk/Test/DeviceVkTest.cpp @@ -0,0 +1,546 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include + +#include "Magnum/Vk/Device.h" +#include "Magnum/Vk/DeviceProperties.h" +#include "Magnum/Vk/Extensions.h" +#include "Magnum/Vk/ExtensionProperties.h" +#include "Magnum/Vk/Handle.h" +#include "Magnum/Vk/Instance.h" +#include "Magnum/Vk/LayerProperties.h" +#include "Magnum/Vk/Result.h" +#include "Magnum/Vk/Version.h" + +#include "MagnumExternal/Vulkan/flextVkGlobal.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct DeviceVkTest: TestSuite::Tester { + explicit DeviceVkTest(); + + void createInfoConstruct(); + void createInfoConstructImplicitDevice(); + void createInfoConstructNoImplicitExtensions(); + void createInfoExtensions(); + void createInfoCopiedStrings(); + void createInfoNoQueuePriorities(); + + void construct(); + void constructExtensions(); + void constructExtensionsCommandLineDisable(); + void constructExtensionsCommandLineEnable(); + void constructMove(); + void constructUnknownExtension(); + void constructNoQueue(); + void wrap(); + void populateGlobalFunctionPointers(); + + Instance _instance; +}; + +struct { + const char* nameDisable; + const char* nameEnable; + Containers::Array argsDisable, argsEnable; + bool driverVersionSupported, debugMarkerEnabled, maintenance1Enabled; + const char* log; +} ConstructCommandLineData[] { + /* Shouldn't print anything about version, enabled layers/exts if quier + output is enabled. */ + {"quiet", "quiet, enabled extensions", + Containers::array({"", "--magnum-log", "quiet"}), + Containers::array({"", "--magnum-log", "quiet", + "--magnum-enable-extensions", "VK_EXT_debug_marker VK_KHR_maintenance1"}), + true, true, true, + ""}, + {"", "enabled extensions", nullptr, + Containers::array({"", + "--magnum-enable-extensions", "VK_EXT_debug_marker VK_KHR_maintenance1"}), + true, true, true, + "Device version: Vulkan {}.{}{}\n" + "Enabled device extensions:\n" + " VK_EXT_debug_marker\n" + " VK_KHR_maintenance1\n"}, + {"forced version", "forced version, enabled extensions", + Containers::array({"", + "--magnum-vulkan-version", "1.0"}), + Containers::array({"", + "--magnum-vulkan-version", "1.0", + "--magnum-enable-extensions", "VK_EXT_debug_marker VK_KHR_maintenance1"}), + false, true, true, + "Device version: Vulkan 1.0\n" + "Enabled device extensions:\n" + " VK_EXT_debug_marker\n" + " VK_KHR_maintenance1\n"}, + {"disabled one extension", "enabled one extension", + Containers::array({"", + "--magnum-disable-extensions", "VK_EXT_debug_marker"}), + Containers::array({"", + "--magnum-enable-extensions", "VK_KHR_maintenance1"}), + true, false, true, + "Device version: Vulkan {}.{}{}\n" + "Enabled device extensions:\n" + " VK_KHR_maintenance1\n"}, + {"disabled extensions", "", + Containers::array({"", + "--magnum-disable-extensions", "VK_EXT_debug_marker VK_KHR_maintenance1"}), + nullptr, + true, false, false, + "Device version: Vulkan {}.{}{}\n"}, +}; + +DeviceVkTest::DeviceVkTest(): _instance{InstanceCreateInfo{arguments().first, arguments().second}} { + addTests({&DeviceVkTest::createInfoConstruct, + &DeviceVkTest::createInfoConstructImplicitDevice, + &DeviceVkTest::createInfoConstructNoImplicitExtensions, + &DeviceVkTest::createInfoExtensions, + &DeviceVkTest::createInfoCopiedStrings, + &DeviceVkTest::createInfoNoQueuePriorities, + + &DeviceVkTest::construct, + &DeviceVkTest::constructExtensions}); + + addInstancedTests({&DeviceVkTest::constructExtensionsCommandLineDisable, + &DeviceVkTest::constructExtensionsCommandLineEnable}, + Containers::arraySize(ConstructCommandLineData)); + + addTests({&DeviceVkTest::constructMove, + &DeviceVkTest::constructUnknownExtension, + &DeviceVkTest::constructNoQueue, + + &DeviceVkTest::wrap, + &DeviceVkTest::populateGlobalFunctionPointers}); +} + +using namespace Containers::Literals; + +void DeviceVkTest::createInfoConstruct() { + DeviceCreateInfo info{pickDevice(_instance)}; + CORRADE_VERIFY(info->sType); + CORRADE_VERIFY(!info->pNext); + /* Extensions might or might not be enabled */ +} + +void DeviceVkTest::createInfoConstructImplicitDevice() { + DeviceCreateInfo info{_instance}; + CORRADE_VERIFY(info->sType); + CORRADE_VERIFY(!info->pNext); + /* Extensions might or might not be enabled */ +} + +void DeviceVkTest::createInfoConstructNoImplicitExtensions() { + DeviceCreateInfo info{_instance, DeviceCreateInfo::Flag::NoImplicitExtensions}; + CORRADE_VERIFY(info->sType); + CORRADE_VERIFY(!info->pNext); + /* No extensions enabled as we explicitly disabled that */ + CORRADE_VERIFY(!info->ppEnabledExtensionNames); + CORRADE_COMPARE(info->enabledExtensionCount, 0); +} + +void DeviceVkTest::createInfoExtensions() { + if(std::getenv("MAGNUM_DISABLE_EXTENSIONS")) + CORRADE_SKIP("Can't test with the MAGNUM_DISABLE_EXTENSIONS environment variable set"); + + DeviceCreateInfo info{_instance, DeviceCreateInfo::Flag::NoImplicitExtensions}; + CORRADE_VERIFY(!info->ppEnabledExtensionNames); + CORRADE_COMPARE(info->enabledExtensionCount, 0); + + info.addEnabledExtensions(); + CORRADE_VERIFY(info->ppEnabledExtensionNames); + CORRADE_COMPARE(info->enabledExtensionCount, 1); + /* The pointer should be to the global data */ + CORRADE_COMPARE(static_cast(info->ppEnabledExtensionNames[0]), + Extensions::KHR::maintenance1::string().data()); + + info.addEnabledExtensions({ + Extensions::KHR::draw_indirect_count{}, + Extensions::KHR::get_memory_requirements2{} + }); + CORRADE_COMPARE(info->enabledExtensionCount, 3); + /* The pointer should be to the global data */ + CORRADE_COMPARE(static_cast(info->ppEnabledExtensionNames[0]), + Extensions::KHR::maintenance1::string().data()); + CORRADE_COMPARE(static_cast(info->ppEnabledExtensionNames[1]), + Extensions::KHR::draw_indirect_count::string().data()); + CORRADE_COMPARE(static_cast(info->ppEnabledExtensionNames[2]), + Extensions::KHR::get_memory_requirements2::string().data()); +} + +void DeviceVkTest::createInfoCopiedStrings() { + Containers::StringView globalButNotNullTerminated = "VK_KHR_maintenance25"_s.except(1); + Containers::String localButNullTerminated = Extensions::KHR::draw_indirect_count::string(); + + DeviceCreateInfo info{_instance, DeviceCreateInfo::Flag::NoImplicitExtensions}; + info.addEnabledExtensions({ + globalButNotNullTerminated, + localButNullTerminated + }); + CORRADE_COMPARE(info->enabledExtensionCount, 2); + + CORRADE_COMPARE(info->ppEnabledExtensionNames[0], globalButNotNullTerminated); + CORRADE_VERIFY(info->ppEnabledExtensionNames[0] != globalButNotNullTerminated.data()); + + CORRADE_COMPARE(info->ppEnabledExtensionNames[1], localButNullTerminated); + CORRADE_VERIFY(info->ppEnabledExtensionNames[1] != localButNullTerminated.data()); +} + +void DeviceVkTest::createInfoNoQueuePriorities() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + DeviceCreateInfo{_instance}.addQueues(0, {}); + CORRADE_COMPARE(out.str(), "Vk::DeviceCreateInfo::addQueues(): at least one queue priority has to be specified\n"); +} + +void DeviceVkTest::construct() { + if(std::getenv("MAGNUM_VULKAN_VERSION")) + CORRADE_SKIP("Can't test with the MAGNUM_VULKAN_VERSION environment variable set"); + + { + DeviceProperties deviceProperties = pickDevice(_instance); + Device device{_instance, DeviceCreateInfo{deviceProperties} + .addQueues(0, {0.0f}) + }; + CORRADE_VERIFY(device.handle()); + /* Device function pointers should be populated */ + CORRADE_VERIFY(device->CreateBuffer); + CORRADE_COMPARE(device.handleFlags(), HandleFlag::DestroyOnDestruction); + CORRADE_COMPARE(device.version(), deviceProperties.apiVersion()); + /* Device version is supported */ + CORRADE_VERIFY(device.isVersionSupported(deviceProperties.apiVersion())); + CORRADE_VERIFY(!device.isVersionSupported(Version::None)); + /* No extensions are enabled by default ... */ + CORRADE_VERIFY(!device.isExtensionEnabled()); + /* ... and thus also no function pointers loaded */ + CORRADE_VERIFY(!device->CmdDebugMarkerInsertEXT); + } + + /* Shouldn't crash or anything */ + CORRADE_VERIFY(true); +} + +void DeviceVkTest::constructExtensions() { + if(std::getenv("MAGNUM_DISABLE_EXTENSIONS")) + CORRADE_SKIP("Can't test with the MAGNUM_DISABLE_EXTENSIONS environment variable set"); + + if(!enumerateLayerProperties().isSupported("VK_LAYER_KHRONOS_validation")) + CORRADE_SKIP("VK_LAYER_KHRONOS_validation not supported, can't test"); + + /* Creating a dedicated instance so we can pass custom args and enable + layers & exts independently */ + Instance instance{InstanceCreateInfo{} + .addEnabledLayers({"VK_LAYER_KHRONOS_validation"}) + /* Needed by VK_EXT_debug_marker */ + .addEnabledExtensions() + }; + + DeviceProperties deviceProperties = pickDevice(instance); + ExtensionProperties extensions = deviceProperties.enumerateExtensionProperties({"VK_LAYER_KHRONOS_validation"}); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_EXT_debug_marker not supported, can't test"); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_KHR_maintenance1 not supported, can't test"); + + Device device{instance, DeviceCreateInfo{deviceProperties} + .addQueues(0, {0.0f}) + .addEnabledExtensions({ + Extensions::EXT::debug_marker::string(), + "VK_KHR_maintenance1"_s + })}; + CORRADE_VERIFY(device.handle()); + + /* Extensions should be reported as enabled ... */ + CORRADE_VERIFY(device.isExtensionEnabled()); + CORRADE_VERIFY(device.isExtensionEnabled(Extensions::KHR::maintenance1{})); + /* ... and function pointers loaded */ + CORRADE_VERIFY(device->CmdDebugMarkerInsertEXT); + CORRADE_VERIFY(device->TrimCommandPoolKHR); +} + +void DeviceVkTest::constructExtensionsCommandLineDisable() { + auto&& data = ConstructCommandLineData[testCaseInstanceId()]; + setTestCaseDescription(data.nameDisable); + + if(std::getenv("MAGNUM_VULKAN_VERSION")) + CORRADE_SKIP("Can't test with the MAGNUM_VULKAN_VERSION environment variable set"); + if(std::getenv("MAGNUM_DISABLE_LAYERS")) + CORRADE_SKIP("Can't test with the MAGNUM_DISABLE_LAYERS environment variable set"); + if(std::getenv("MAGNUM_DISABLE_EXTENSIONS")) + CORRADE_SKIP("Can't test with the MAGNUM_DISABLE_EXTENSIONS environment variable set"); + + if(!enumerateLayerProperties().isSupported("VK_LAYER_KHRONOS_validation")) + CORRADE_SKIP("VK_LAYER_KHRONOS_validation not supported, can't test"); + + /* Creating a dedicated instance so we can pass custom args and enable + layers independently */ + Instance instance{InstanceCreateInfo{Int(data.argsDisable.size()), data.argsDisable} + .addEnabledLayers({"VK_LAYER_KHRONOS_validation"}) + /* Needed by VK_EXT_debug_marker */ + .addEnabledExtensions() + }; + + DeviceProperties deviceProperties = pickDevice(instance); + ExtensionProperties extensions = deviceProperties.enumerateExtensionProperties({"VK_LAYER_KHRONOS_validation"}); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_EXT_debug_marker not supported, can't test"); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_KHR_maintenance1 not supported, can't test"); + + std::ostringstream out; + Debug redirectOutput{&out}; + Device device{instance, DeviceCreateInfo{deviceProperties, DeviceCreateInfo::Flag::NoImplicitExtensions} + .addQueues(0, {0.0f}) + .addEnabledExtensions< + Extensions::EXT::debug_marker, + Extensions::KHR::maintenance1 + >()}; + CORRADE_VERIFY(device.handle()); + CORRADE_COMPARE(device.isVersionSupported(deviceProperties.apiVersion()), data.driverVersionSupported); + CORRADE_COMPARE(device.isExtensionEnabled(), data.debugMarkerEnabled); + CORRADE_COMPARE(device.isExtensionEnabled(), data.maintenance1Enabled); + + /** @todo cleanup when Debug::toString() or some similar utility exists */ + UnsignedInt major = versionMajor(deviceProperties.apiVersion()); + UnsignedInt minor = versionMinor(deviceProperties.apiVersion()); + UnsignedInt patch = versionPatch(deviceProperties.apiVersion()); + /* SwiftShader reports just 1.1 with no patch version, special-case that */ + CORRADE_COMPARE(out.str(), Utility::formatString(data.log, major, minor, patch ? Utility::formatString(".{}", patch) : "")); + + /* Verify that the entrypoint is actually (not) loaded as expected, to + avoid all the above reporting being just smoke & mirrors */ + CORRADE_COMPARE(!!device->CmdDebugMarkerInsertEXT, data.debugMarkerEnabled); + CORRADE_COMPARE(!!device->TrimCommandPoolKHR, data.maintenance1Enabled); +} + +void DeviceVkTest::constructExtensionsCommandLineEnable() { + auto&& data = ConstructCommandLineData[testCaseInstanceId()]; + setTestCaseDescription(data.nameEnable); + + if(std::getenv("MAGNUM_VULKAN_VERSION")) + CORRADE_SKIP("Can't test with the MAGNUM_VULKAN_VERSION environment variable set"); + if(std::getenv("MAGNUM_DISABLE_LAYERS")) + CORRADE_SKIP("Can't test with the MAGNUM_DISABLE_LAYERS environment variable set"); + if(std::getenv("MAGNUM_DISABLE_EXTENSIONS")) + CORRADE_SKIP("Can't test with the MAGNUM_DISABLE_EXTENSIONS environment variable set"); + + if(!enumerateLayerProperties().isSupported("VK_LAYER_KHRONOS_validation")) + CORRADE_SKIP("VK_LAYER_KHRONOS_validation not supported, can't test"); + + /* Creating a dedicated instance so we can pass custom args and enable + layers independently */ + Instance instance{InstanceCreateInfo{Int(data.argsEnable.size()), data.argsEnable} + .addEnabledLayers({"VK_LAYER_KHRONOS_validation"}) + /* Needed by VK_EXT_debug_marker */ + .addEnabledExtensions() + }; + + DeviceProperties deviceProperties = pickDevice(instance); + ExtensionProperties extensions = deviceProperties.enumerateExtensionProperties({"VK_LAYER_KHRONOS_validation"}); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_EXT_debug_marker not supported, can't test"); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_KHR_maintenance1 not supported, can't test"); + + std::ostringstream out; + Debug redirectOutput{&out}; + Device device{instance, DeviceCreateInfo{instance, DeviceCreateInfo::Flag::NoImplicitExtensions} + .addQueues(0, {0.0f}) + /* Nothing enabled by the application */ + }; + CORRADE_VERIFY(device.handle()); + CORRADE_COMPARE(device.isVersionSupported(deviceProperties.apiVersion()), data.driverVersionSupported); + CORRADE_COMPARE(device.isExtensionEnabled(), data.debugMarkerEnabled); + CORRADE_COMPARE(device.isExtensionEnabled(), data.maintenance1Enabled); + + /** @todo cleanup when Debug::toString() or some similar utility exists */ + UnsignedInt major = versionMajor(deviceProperties.apiVersion()); + UnsignedInt minor = versionMinor(deviceProperties.apiVersion()); + UnsignedInt patch = versionPatch(deviceProperties.apiVersion()); + /* SwiftShader reports just 1.1 with no patch version, special-case that */ + CORRADE_COMPARE(out.str(), Utility::formatString(data.log, major, minor, patch ? Utility::formatString(".{}", patch) : "")); + + /* Verify that the entrypoint is actually (not) loaded as expected, to + avoid all the above reporting being just smoke & mirrors */ + CORRADE_COMPARE(!!device->CmdDebugMarkerInsertEXT, data.debugMarkerEnabled); + CORRADE_COMPARE(!!device->TrimCommandPoolKHR, data.maintenance1Enabled); +} + +void DeviceVkTest::constructMove() { + DeviceProperties deviceProperties = pickDevice(_instance); + ExtensionProperties extensions = deviceProperties.enumerateExtensionProperties(); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_KHR_maintenance1 not supported, can't test"); + + Device a{_instance, DeviceCreateInfo{deviceProperties} + .addQueues(0, {0.0f}) + .addEnabledExtensions() + }; + VkDevice handle = a.handle(); + Version version = a.version(); + CORRADE_VERIFY(handle); + CORRADE_VERIFY(version != Version{}); + CORRADE_VERIFY(version != Version::None); + + Device b = std::move(a); + CORRADE_VERIFY(!a.handle()); + CORRADE_COMPARE(b.handleFlags(), HandleFlag::DestroyOnDestruction); + CORRADE_COMPARE(b.handle(), handle); + CORRADE_COMPARE(b.version(), version); + CORRADE_VERIFY(b.isExtensionEnabled()); + /* Function pointers in a are left in whatever state they were before, as + that doesn't matter */ + CORRADE_VERIFY(b->CreateBuffer); + + Device c{NoCreate}; + c = std::move(b); + CORRADE_VERIFY(!b.handle()); + CORRADE_COMPARE(b.handleFlags(), HandleFlags{}); + CORRADE_COMPARE(c.handleFlags(), HandleFlag::DestroyOnDestruction); + CORRADE_COMPARE(c.handle(), handle); + CORRADE_COMPARE(c.version(), version); + CORRADE_VERIFY(c.isExtensionEnabled()); + /* Everything is swapped, including function pointers */ + CORRADE_VERIFY(!b->CreateBuffer); + CORRADE_VERIFY(c->CreateBuffer); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void DeviceVkTest::constructUnknownExtension() { + CORRADE_SKIP("Currently this hits an internal assert, which can't be tested."); + + std::ostringstream out; + Error redirectError{&out}; + Device device{_instance, DeviceCreateInfo{_instance} + .addQueues(0, {0.0f}) + .addEnabledExtensions({"VK_this_doesnt_exist"_s})}; + CORRADE_COMPARE(out.str(), "TODO"); +} + +void DeviceVkTest::constructNoQueue() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + Device device{_instance, DeviceCreateInfo{_instance}}; + CORRADE_COMPARE(out.str(), "Vk::Device: needs to be created with at least one queue\n"); +} + +void DeviceVkTest::wrap() { + if(std::getenv("MAGNUM_VULKAN_VERSION")) + CORRADE_SKIP("Can't test with the MAGNUM_VULKAN_VERSION environment variable set"); + if(std::getenv("MAGNUM_DISABLE_LAYERS")) + CORRADE_SKIP("Can't test with the MAGNUM_DISABLE_LAYERS environment variable set"); + if(std::getenv("MAGNUM_DISABLE_EXTENSIONS")) + CORRADE_SKIP("Can't test with the MAGNUM_DISABLE_EXTENSIONS environment variable set"); + + if(!enumerateLayerProperties().isSupported("VK_LAYER_KHRONOS_validation")) + CORRADE_SKIP("VK_LAYER_KHRONOS_validation not supported, can't test"); + + /* Creating a dedicated instance so we can enable layers independently */ + Instance instance{InstanceCreateInfo{} + .addEnabledLayers({"VK_LAYER_KHRONOS_validation"}) + /* Needed by VK_EXT_debug_marker */ + .addEnabledExtensions() + }; + + DeviceProperties deviceProperties = pickDevice(instance); + ExtensionProperties extensions = deviceProperties.enumerateExtensionProperties({"VK_LAYER_KHRONOS_validation"}); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_EXT_debug_marker not supported, can't test"); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_KHR_maintenance1 not supported, can't test"); + + VkDevice device; + CORRADE_COMPARE(Result(instance->CreateDevice(deviceProperties, + DeviceCreateInfo{instance} + .addQueues(0, {0.0f}) + .addEnabledExtensions< + Extensions::EXT::debug_marker, + Extensions::KHR::maintenance1 + >(), + nullptr, &device)), Result::Success); + CORRADE_VERIFY(device); + + { + /* Wrapping should load the basic function pointers */ + auto wrapped = Device::wrap(instance, device, Version::Vk11, { + Extensions::EXT::debug_marker::string() + }, HandleFlag::DestroyOnDestruction); + CORRADE_VERIFY(wrapped->DestroyDevice); + + /* Specified version should be reported as supported but higher not + regardless of the actual driver version */ + CORRADE_VERIFY(wrapped.isVersionSupported(Version::Vk11)); + CORRADE_VERIFY(!wrapped.isVersionSupported(Version::Vk12)); + + /* Listed extensions should be reported as enabled and function + pointers loaded as well */ + CORRADE_VERIFY(wrapped.isExtensionEnabled()); + CORRADE_VERIFY(wrapped->CmdDebugMarkerInsertEXT); + + /* Unlisted not, but function pointers should still be loaded as the + actual instance does have the extension enabled */ + CORRADE_VERIFY(!wrapped.isExtensionEnabled()); + CORRADE_VERIFY(wrapped->TrimCommandPoolKHR); + + /* 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(instance, device, Version::Vk10, {}); + CORRADE_VERIFY(wrapped->DestroyDevice); + wrapped->DestroyDevice(device, nullptr); +} + +void DeviceVkTest::populateGlobalFunctionPointers() { + vkDestroyDevice = nullptr; + + Device device{_instance, DeviceCreateInfo{_instance} + .addQueues(0, {0.0f}) + }; + CORRADE_VERIFY(!vkDestroyDevice); + device.populateGlobalFunctionPointers(); + CORRADE_VERIFY(vkDestroyDevice); + CORRADE_VERIFY(vkDestroyDevice == device->DestroyDevice); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::DeviceVkTest) diff --git a/src/Magnum/Vk/Vk.h b/src/Magnum/Vk/Vk.h index 1b618f40e..89f2af7a4 100644 --- a/src/Magnum/Vk/Vk.h +++ b/src/Magnum/Vk/Vk.h @@ -36,6 +36,8 @@ namespace Magnum { namespace Vk { #ifndef DOXYGEN_GENERATING_OUTPUT +class Device; +class DeviceCreateInfo; class DeviceProperties; enum class DeviceType: Int; class Extension;