diff --git a/doc/snippets/MagnumVk.cpp b/doc/snippets/MagnumVk.cpp index dcf1e6b2d..7f446cb49 100644 --- a/doc/snippets/MagnumVk.cpp +++ b/doc/snippets/MagnumVk.cpp @@ -23,13 +23,48 @@ DEALINGS IN THE SOFTWARE. */ +#include + #include "Magnum/Magnum.h" #include "Magnum/Math/Color.h" +#include "Magnum/Vk/Extensions.h" +#include "Magnum/Vk/Instance.h" #include "Magnum/Vk/Integration.h" +#include "MagnumExternal/Vulkan/flextVkGlobal.h" using namespace Magnum; int main() { +{ +Vk::Instance instance; +/* [Instance-isExtensionEnabled] */ +if(instance.isExtensionEnabled()) { + // use the fancy debugging APIs +} else if(instance.isExtensionEnabled()) { + // use the non-fancy and deprecated debugging APIs +} else { + // well, tough luck +} +/* [Instance-isExtensionEnabled] */ +} + +{ +/* Header included again inside a function, but it's fine as the guards will + make it empty */ +/* [Instance-global-function-pointers] */ +#include + +// … + +Vk::Instance instance; +instance.populateGlobalFunctionPointers(); + +VkPhysicalDeviceGroupProperties properties[10]; +UnsignedInt count = Containers::arraySize(properties); +vkEnumeratePhysicalDeviceGroupsKHR(instance, &count, properties); +/* [Instance-global-function-pointers] */ +} + { /* [Integration] */ VkOffset2D a{64, 32}; diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index 02ce1316f..0d7724e86 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -136,7 +136,7 @@ Vulkan function | Matching API @fn_vk{CreateFramebuffer}, \n @fn_vk{DestroyFramebuffer} | | @fn_vk{CreateImage}, \n @fn_vk{DestroyImage} | | @fn_vk{CreateImageView}, \n @fn_vk{DestroyImageView} | | -@fn_vk{CreateInstance}, \n @fn_vk{DestroyInstance} | | +@fn_vk{CreateInstance}, \n @fn_vk{DestroyInstance} | @ref Instance constructor and destructor @fn_vk{CreatePipeline}, \n @fn_vk{DestroyPipeline} | | @fn_vk{CreatePipelineCache}, \n @fn_vk{DestroyPipelineCache} | | @fn_vk{CreatePipelineLayout}, \n @fn_vk{DestroyPipelineLayout} | | @@ -200,7 +200,7 @@ Vulkan function | Matching API @fn_vk{GetImageMemoryRequirements}, \n @fn_vk{GetImageMemoryRequirements2} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{GetImageSparseMemoryRequirements}, \n @fn_vk{GetImageSparseMemoryRequirements2} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{GetImageSubresourceLayout} | | -@fn_vk{GetInstanceProcAddr} | | +@fn_vk{GetInstanceProcAddr} | @ref Instance constructor @fn_vk{GetPhysicalDeviceExternalBufferProperties} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{GetPhysicalDeviceExternalFenceProperties} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @fn_vk{GetPhysicalDeviceExternalSemaphoreProperties} @m_class{m-label m-flat m-success} **KHR, 1.1** | | @@ -294,6 +294,24 @@ Vulkan function | Matching API --------------------------------------- | ------------ @fn_vk{WaitForFences} | | +@section vulkan-mapping-structures Info structures + +@subsection vulkan-mapping-structures-a A + +@m_class{m-fullwidth} + +Vulkan structure | Matching API +--------------------------------------- | ------------ +@type_vk{ApplicationInfo} | @ref InstanceCreateInfo + +@subsection vulkan-mapping-structures-i I + +@m_class{m-fullwidth} + +Vulkan structure | Matching API +--------------------------------------- | ------------ +@type_vk{InstanceCreateInfo} | @ref InstanceCreateInfo + */ }} diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index c61905bf8..07246bb36 100644 --- a/src/Magnum/Vk/CMakeLists.txt +++ b/src/Magnum/Vk/CMakeLists.txt @@ -29,8 +29,12 @@ find_package(Vulkan REQUIRED) set(MagnumVk_SRCS Extensions.cpp Handle.cpp + Instance.cpp Result.cpp - Version.cpp) + Version.cpp + + Implementation/Arguments.cpp + Implementation/InstanceState.cpp) set(MagnumVk_GracefulAssert_SRCS Enums.cpp @@ -55,6 +59,9 @@ set(MagnumVk_HEADERS visibility.h) set(MagnumVk_PRIVATE_HEADERS + Implementation/Arguments.h + Implementation/InstanceState.h + Implementation/compressedPixelFormatMapping.hpp Implementation/pixelFormatMapping.hpp Implementation/vertexFormatMapping.hpp) diff --git a/src/Magnum/Vk/Extensions.cpp b/src/Magnum/Vk/Extensions.cpp index b34631784..3f610df63 100644 --- a/src/Magnum/Vk/Extensions.cpp +++ b/src/Magnum/Vk/Extensions.cpp @@ -31,9 +31,9 @@ namespace Magnum { namespace Vk { namespace { -/* When adding a new list, InstanceExtension::extensions() needs to be adapted. - Binary search is performed on the extensions, thus they have to be sorted - alphabetically. */ +/* When adding a new list, InstanceExtension::extensions() and + Instance::initialize() needs to be adapted. Binary search is performed on + the extensions, thus they have to be sorted alphabetically. */ constexpr InstanceExtension InstanceExtensions[] { Extensions::EXT::debug_report{}, Extensions::EXT::debug_utils{}, diff --git a/src/Magnum/Vk/Extensions.h b/src/Magnum/Vk/Extensions.h index f35d26d0d..557de7aaf 100644 --- a/src/Magnum/Vk/Extensions.h +++ b/src/Magnum/Vk/Extensions.h @@ -50,7 +50,8 @@ Each struct has the same public methods as the @ref InstanceExtension / @ref Extension class (@ref Extension::requiredVersion() "requiredVersion()", @ref Extension::coreVersion() "coreVersion()" and @ref Extension::string() "string()"), but these structs are better suited for compile-time decisions rather than -@ref Extension instances. +@ref Extension instances. See @ref Instance::isExtensionEnabled() for example +usage. This library is built if `WITH_VK` is enabled when building Magnum. To use this library with CMake, you need to request the `Vk` component of the `Magnum` diff --git a/src/Magnum/Vk/Implementation/Arguments.cpp b/src/Magnum/Vk/Implementation/Arguments.cpp new file mode 100644 index 000000000..1b64c25f0 --- /dev/null +++ b/src/Magnum/Vk/Implementation/Arguments.cpp @@ -0,0 +1,49 @@ +/* + 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 "Arguments.h" + +#include + +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("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("vulkan-version").setHelp("vulkan-version", "force Vulkan version", "X.Y") + .addOption("log", "default").setHelp("log", "console logging", "default|quiet|verbose") + .setFromEnvironment("disable-layers") + .setFromEnvironment("disable-extensions") + .setFromEnvironment("enable-layers") + .setFromEnvironment("enable-instance-extensions") + .setFromEnvironment("vulkan-version") + .setFromEnvironment("log"); + return args; +} + +}}} diff --git a/src/Magnum/Vk/Implementation/Arguments.h b/src/Magnum/Vk/Implementation/Arguments.h new file mode 100644 index 000000000..0c2fb36f0 --- /dev/null +++ b/src/Magnum/Vk/Implementation/Arguments.h @@ -0,0 +1,40 @@ +#ifndef Magnum_Vk_Implementation_Arguments_h +#define Magnum_Vk_Implementation_Arguments_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 + +#include "Magnum/Magnum.h" + +namespace Magnum { namespace Vk { namespace Implementation { + +/* Used by both InstanceCreateInfo and DeviceCreateInfo, each takes a subset + of the arguments */ +Utility::Arguments arguments(); + +}}} + +#endif diff --git a/src/Magnum/Vk/Implementation/InstanceState.cpp b/src/Magnum/Vk/Implementation/InstanceState.cpp new file mode 100644 index 000000000..ffd0c0337 --- /dev/null +++ b/src/Magnum/Vk/Implementation/InstanceState.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 "InstanceState.h" + +namespace Magnum { namespace Vk { namespace Implementation { + +InstanceState::InstanceState(Instance&, Int argc, const char** argv): argc{argc}, argv{argv} {} + +}}} diff --git a/src/Magnum/Vk/Implementation/InstanceState.h b/src/Magnum/Vk/Implementation/InstanceState.h new file mode 100644 index 000000000..077464da6 --- /dev/null +++ b/src/Magnum/Vk/Implementation/InstanceState.h @@ -0,0 +1,42 @@ +#ifndef Magnum_Vk_Implementation_InstanceState_h +#define Magnum_Vk_Implementation_InstanceState_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 InstanceState { + explicit InstanceState(Instance& instance, Int argc, const char** argv); + + Int argc; + const char** argv; +}; + +}}} + +#endif diff --git a/src/Magnum/Vk/Instance.cpp b/src/Magnum/Vk/Instance.cpp new file mode 100644 index 000000000..ed033f387 --- /dev/null +++ b/src/Magnum/Vk/Instance.cpp @@ -0,0 +1,348 @@ +/* + 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 "Instance.h" + +#include +#include +#include +#include +#include + +#include "Magnum/Vk/Extensions.h" +#include "Magnum/Vk/Handle.h" +#include "Magnum/Vk/Result.h" +#include "Magnum/Vk/Version.h" +#include "Magnum/Vk/Implementation/Arguments.h" +#include "Magnum/Vk/Implementation/InstanceState.h" +#include "MagnumExternal/Vulkan/flextVkGlobal.h" + +namespace Magnum { namespace Vk { + +struct InstanceCreateInfo::State { + Containers::String applicationName; + Containers::Array ownedStrings; + Containers::Array layers; + Containers::Array extensions; + + Containers::String disabledLayersStorage, disabledExtensionsStorage; + Containers::Array disabledLayers, disabledExtensions; + bool quietLog = false; + Version version = Version::None; + Int argc; + const char** argv; +}; + +InstanceCreateInfo::InstanceCreateInfo(const Int argc, const char** const argv, const LayerProperties* const layerProperties, const InstanceExtensionProperties* const extensionProperties, const Flags flags): _info{}, _applicationInfo{} { + Utility::Arguments args = Implementation::arguments(); + args.parse(argc, argv); + + if(args.value("log") == "quiet") + _state.emplace().quietLog = true; + if(argc && argv) { + if(!_state) _state.emplace(); + _state->argc = argc; + _state->argv = argv; + } + + _info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + /** @todo filter out magnum-specific flags once there are any */ + _info.flags = VkInstanceCreateFlags(flags); + _info.pApplicationInfo = &_applicationInfo; + _applicationInfo.pEngineName = "Magnum"; + /** @todo magnum version? 2020 can't fit into Vulkan's version + representation, sigh */ + + /* If there's a forced Vulkan version, use that, otherwise use the reported + instance version */ + Containers::StringView version = args.value("vulkan-version"); + if(!version.isEmpty()) { + if(!_state) _state.emplace(); + + if((_state->version = args.value("vulkan-version")) == Version::None) + Warning{} << "Invalid --magnum-vulkan-version" << args.value("vulkan-version") << Debug::nospace << ", ignoring"; + } + if(_state && _state->version == Version::None) + _state->version = enumerateInstanceVersion(); + _applicationInfo.apiVersion = UnsignedInt(_state ? _state->version : enumerateInstanceVersion()); + + /* If there are any disabled layers or extensions, sort them and save for + later -- we'll use them to filter the ones added by the app */ + Containers::String disabledLayers = args.value("disable-layers"); + Containers::String disabledExtensions = args.value("disable-extensions"); + if(!disabledLayers.isEmpty()) { + if(!_state) _state.emplace(); + + _state->disabledLayersStorage = std::move(disabledLayers); + _state->disabledLayers = Containers::StringView{_state->disabledLayersStorage}.splitWithoutEmptyParts(); + std::sort(_state->disabledLayers.begin(), _state->disabledLayers.end()); + } + if(!disabledExtensions.isEmpty()) { + if(!_state) _state.emplace(); + + _state->disabledExtensionsStorage = std::move(disabledExtensions); + _state->disabledExtensions = Containers::StringView{_state->disabledExtensionsStorage}.splitWithoutEmptyParts(); + std::sort(_state->disabledExtensions.begin(), _state->disabledExtensions.end()); + } + + /* Add all layers and 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) */ + addEnabledLayers(args.value("enable-layers").splitWithoutEmptyParts()); + addEnabledExtensions(args.value("enable-instance-extensions").splitWithoutEmptyParts()); + + /** @todo use this (enabling debug layers etc.) */ + static_cast(layerProperties); + static_cast(extensionProperties); +} + +InstanceCreateInfo::InstanceCreateInfo(NoInitT) noexcept {} + +InstanceCreateInfo::InstanceCreateInfo(const VkInstanceCreateInfo& info): _info{info} {} + +InstanceCreateInfo::~InstanceCreateInfo() = default; + +InstanceCreateInfo& InstanceCreateInfo::setApplicationInfo(const Containers::StringView name, const Version version) { + /* Keep an owned copy of the name if it's not global / null-terminated; + Use nullptr if the view is empty */ + if(!name.isEmpty()) { + if(!_state) _state.emplace(); + + _state->applicationName = Containers::String::nullTerminatedGlobalView(name); + _applicationInfo.pApplicationName = _state->applicationName.data(); + } else { + if(_state) _state->applicationName = nullptr; + _applicationInfo.pApplicationName = nullptr; + } + + _applicationInfo.applicationVersion = UnsignedInt(version); + return *this; +} + +InstanceCreateInfo& InstanceCreateInfo::addEnabledLayers(const Containers::ArrayView layers) { + if(layers.empty()) return *this; + if(!_state) _state.emplace(); + + /* Add null-terminated strings to the layer array */ + arrayReserve(_state->layers, _state->layers.size() + layers.size()); + for(const Containers::StringView layer: layers) { + /* If the layer is blacklisted, skip it */ + if(std::binary_search(_state->disabledLayers.begin(), _state->disabledLayers.end(), layer)) 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(!(layer.flags() >= (Containers::StringViewFlag::NullTerminated|Containers::StringViewFlag::Global))) + data = arrayAppend(_state->ownedStrings, Containers::InPlaceInit, + Containers::AllocatedInit, layer).data(); + else data = layer.data(); + + arrayAppend(_state->layers, data); + } + + /* Update the layer count, re-route the pointer to the layers array in case + it got reallocated */ + _info.enabledLayerCount = _state->layers.size(); + _info.ppEnabledLayerNames = _state->layers.data(); + return *this; +} + +InstanceCreateInfo& InstanceCreateInfo::addEnabledLayers(const std::initializer_list layers) { + return addEnabledLayers(Containers::arrayView(layers)); +} + +InstanceCreateInfo& InstanceCreateInfo::addEnabledExtensions(const Containers::ArrayView extensions) { + if(extensions.empty()) return *this; + 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; +} + +InstanceCreateInfo& InstanceCreateInfo::addEnabledExtensions(const std::initializer_list extensions) { + return addEnabledExtensions(Containers::arrayView(extensions)); +} + +InstanceCreateInfo& InstanceCreateInfo::addEnabledExtensions(const Containers::ArrayView extensions) { + if(extensions.empty()) return *this; + if(!_state) _state.emplace(); + + arrayReserve(_state->extensions, _state->extensions.size() + extensions.size()); + for(const InstanceExtension& 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; +} + +InstanceCreateInfo& InstanceCreateInfo::addEnabledExtensions(const std::initializer_list extensions) { + return addEnabledExtensions(Containers::arrayView(extensions)); +} + +Instance Instance::wrap(const VkInstance 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 */ + Instance out{NoCreate}; + out._handle = handle; + out._flags = flags; + out.initializeExtensions(enabledExtensions); + out.initialize(version, 0, nullptr); + return out; +} + +Instance Instance::wrap(const VkInstance handle, const Version version, const std::initializer_list enabledExtensions, const HandleFlags flags) { + return wrap(handle, version, Containers::arrayView(enabledExtensions), flags); +} + +Instance::Instance(const InstanceCreateInfo& info): _flags{HandleFlag::DestroyOnDestruction} { + const Version version = info._state && info._state->version != Version::None ? info._state->version : enumerateInstanceVersion(); + + /* Print all enabled layers and extensions if we're not told to be quiet */ + if(!info._state || !info._state->quietLog) { + Debug{} << "Instance version:" << version; + + if(info->enabledLayerCount) { + Debug{} << "Enabled layers:"; + for(std::size_t i = 0, max = info->enabledLayerCount; i != max; ++i) + Debug{} << " " << info->ppEnabledLayerNames[i]; + } + + if(info->enabledExtensionCount) { + Debug{} << "Enabled instance extensions:"; + for(std::size_t i = 0, max = info->enabledExtensionCount; i != max; ++i) + Debug{} << " " << info->ppEnabledExtensionNames[i]; + } + } + + MAGNUM_VK_INTERNAL_ASSERT_RESULT(vkCreateInstance(info, nullptr, &_handle)); + + initializeExtensions({info->ppEnabledExtensionNames, info->enabledExtensionCount}); + if(info._state) + initialize(version, info._state->argc, info._state->argv); + else + initialize(version, 0, nullptr); +} + +Instance::Instance(NoCreateT): _handle{}, _functionPointers{} {} + +Instance::Instance(Instance&& 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 = {}; +} + +Instance::~Instance() { + if(_handle && (_flags & HandleFlag::DestroyOnDestruction)) + _functionPointers.DestroyInstance(_handle, nullptr); +} + +Instance& Instance::operator=(Instance&& 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 Instance::initializeExtensions(const Containers::ArrayView enabledExtensions) { + /* Mark all known extensions as enabled */ + for(const T extension: enabledExtensions) { + for(Containers::ArrayView knownExtensions: { + InstanceExtension::extensions(Version::None), + /*InstanceExtension::extensions(Version::Vk10), is empty */ + InstanceExtension::extensions(Version::Vk11), + /*InstanceExtension::extensions(Version::Vk12) is empty */ + }) { + auto found = std::lower_bound(knownExtensions.begin(), knownExtensions.end(), extension, [](const InstanceExtension& a, const T& b) { + return a.string() < static_cast(b); + }); + if(found->string() != extension) continue; + _extensionStatus.set(found->index(), true); + } + } +} + +void Instance::initialize(const Version version, const Int argc, const char** const argv) { + /* Init version, function pointers */ + _version = version; + flextVkInitInstance(_handle, &_functionPointers); + + /* Set up extension-dependent functionality */ + _state.emplace(*this, argc, argv); +} + +VkInstance Instance::release() { + const VkInstance handle = _handle; + _handle = nullptr; + return handle; +} + +bool Instance::isExtensionEnabled(const InstanceExtension& extension) const { + return _extensionStatus[extension.index()]; +} + +void Instance::populateGlobalFunctionPointers() { + flextVkInstance = _functionPointers; +} + +}} diff --git a/src/Magnum/Vk/Instance.h b/src/Magnum/Vk/Instance.h index 77024c35a..352592d0b 100644 --- a/src/Magnum/Vk/Instance.h +++ b/src/Magnum/Vk/Instance.h @@ -26,18 +26,356 @@ */ /** @file - * @brief Nothing, haha + * @brief Class @ref Magnum::Vk::InstanceCreateInfo, @ref Magnum::Vk::Instance * @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 { InstanceExtensionCount = 16 }; + + struct InstanceState; } +/** +@brief Instance creation info +@m_since_latest + +Wraps a @type_vk_keyword{InstanceCreateInfo} and +@type_vk_keyword{ApplicationInfo}. +@see @ref Instance +*/ +class MAGNUM_VK_EXPORT InstanceCreateInfo { + public: + /** + * @brief Instance creation flag + * + * Wraps @type_vk_keyword{InstanceCreateFlagBits}. + * @see @ref Flags, @ref InstanceCreateInfo(Int, const char**, const LayerProperties*, const InstanceExtensionProperties*, Flags) + */ + enum class Flag: UnsignedInt {}; + + /** + * @brief Instance creation flags + * + * Type-safe wrapper for @type_vk_keyword{InstanceCreateFlags}. + * @see @ref InstanceCreateInfo(Int, const char**, const LayerProperties*, const InstanceExtensionProperties*, Flags) + */ + typedef Containers::EnumSet Flags; + + /** + * @brief Constructor + * @param argc Command-line argument count. Can be @cpp 0 @ce. + * @param argv Command-line argument values. Can be + * @cpp nullptr @ce. If set, is expected to stay in scope for the + * whole instance lifetime. + * @param layerProperties Existing @ref LayerProperties instance for + * querying available Vulkan layers. If @cpp nullptr @ce, a new + * instance may be created internally if needed. + * @param extensionProperties Existing @ref InstanceExtensionProperties + * instance for querying available Vulkan extensions. If + * @cpp nullptr @ce, a new instance may be created internally if + * needed. + * @param flags Instance creation flags + * + * The following @type_vk{InstanceCreateInfo} fields are pre-filled in + * addition to `sType`, everything else is zero-filled: + * + * - `pApplicationInfo` + * - @cpp pApplicationInfo->apiVersion @ce to + * @ref enumerateInstanceVersion() + * - @cpp pApplicationInfo->engineName @ce to @cpp "Magnum" @ce + */ + explicit InstanceCreateInfo(Int argc, const char** argv, const LayerProperties* layerProperties, const InstanceExtensionProperties* const extensionProperties, Flags flags = {}); + + /** @overload */ + explicit InstanceCreateInfo(Int argc, char** argv, const LayerProperties* layerProperties, const InstanceExtensionProperties* extensionProperties, Flags flags = {}): InstanceCreateInfo{argc, const_cast(argv), layerProperties, extensionProperties, flags} {} + + /** @overload */ + explicit InstanceCreateInfo(Int argc, std::nullptr_t argv, const LayerProperties* layerProperties, const InstanceExtensionProperties* extensionProperties, Flags flags = {}): InstanceCreateInfo{argc, static_cast(argv), layerProperties, extensionProperties, flags} {} + + /** @overload */ + explicit InstanceCreateInfo(Int argc, const char** argv, Flags flags = {}): InstanceCreateInfo{argc, argv, nullptr, nullptr, flags} {} + + /** @overload */ + explicit InstanceCreateInfo(Int argc, char** argv, Flags flags = {}): InstanceCreateInfo{argc, argv, nullptr, nullptr, flags} {} + + /** @overload */ + explicit InstanceCreateInfo(Int argc, std::nullptr_t argv, Flags flags = {}): InstanceCreateInfo{argc, argv, nullptr, nullptr, flags} {} + + /** @overload */ + explicit InstanceCreateInfo(Flags flags = {}): InstanceCreateInfo{0, nullptr, 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 InstanceCreateInfo(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 InstanceCreateInfo(const VkInstanceCreateInfo& info); + + ~InstanceCreateInfo(); + + /** + * @brief Set application info + * @return Reference to self (for method chaining) + * + * Use the @ref version() helper to create the @p version value. The + * name is @cpp nullptr @ce by default. + */ + InstanceCreateInfo& setApplicationInfo(Containers::StringView name, Version version); + + /** + * @brief Add enabled layers + * @return Reference to self (for method chaining) + * + * All listed layers are expected be supported, use + * @ref LayerProperties::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. + */ + InstanceCreateInfo& addEnabledLayers(Containers::ArrayView layers); + /** @overload */ + InstanceCreateInfo& addEnabledLayers(std::initializer_list layers); + + /** + * @brief Add enabled instance 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 InstanceExtensionProperties::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. + */ + InstanceCreateInfo& addEnabledExtensions(Containers::ArrayView extensions); + /** @overload */ + InstanceCreateInfo& addEnabledExtensions(std::initializer_list extension); + /** @overload */ + InstanceCreateInfo& addEnabledExtensions(Containers::ArrayView extensions); + /** @overload */ + InstanceCreateInfo& addEnabledExtensions(std::initializer_list extension); + /** @overload */ + template InstanceCreateInfo& addEnabledExtensions() { + static_assert(Implementation::IsInstanceExtension::value, "expected only Vulkan instance extensions"); + return addEnabledExtensions({E{}...}); + } + + /** @brief Underlying @type_vk{InstanceCreateInfo} structure */ + VkInstanceCreateInfo& operator*() { return _info; } + /** @overload */ + const VkInstanceCreateInfo& operator*() const { return _info; } + /** @overload */ + VkInstanceCreateInfo* operator->() { return &_info; } + /** @overload */ + const VkInstanceCreateInfo* operator->() const { return &_info; } + /** @overload */ + operator const VkInstanceCreateInfo*() const { return &_info; } + + private: + friend Instance; + + VkInstanceCreateInfo _info; + VkApplicationInfo _applicationInfo; + struct State; + Containers::Pointer _state; +}; + +CORRADE_ENUMSET_OPERATORS(InstanceCreateInfo::Flags) + +/** +@brief Instance +@m_since_latest + +Wraps a @type_vk_keyword{Instance} and stores instance-specific Vulkan function +pointers. +@see @ref vulkan-wrapping +*/ +class MAGNUM_VK_EXPORT Instance { + public: + /** + * @brief Wrap existing Vulkan handle + * @param handle The @type_vk{Instance} 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 of an existing Vulkan 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 empty, the instance will behave as + * if no extensions were enabled. + * + * Unlike an instance created using a constructor, the Vulkan instance + * is by default not deleted on destruction, use @p flags for different + * behavior. + * @see @ref release() + */ + static Instance wrap(VkInstance handle, Version version, Containers::ArrayView enabledExtensions, HandleFlags flags = {}); + + /** @overload */ + static Instance wrap(VkInstance handle, Version version, std::initializer_list enabledExtensions, HandleFlags flags = {}); + + /** + * @brief Constructor + * + * @see @fn_vk_keyword{CreateInstance} + */ + explicit Instance(const InstanceCreateInfo& info = InstanceCreateInfo{}); + + /** + * @brief Construct without creating the instance + * + * 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 Instance(NoCreateT); + + /** @brief Copying is not allowed */ + Instance(const Instance&) = delete; + + /** @brief Move constructor */ + Instance(Instance&& other) noexcept; + + /** + * @brief Destructor + * + * Destroys associated @type_vk{Instance} handle, unless the instance + * was created using @ref wrap() without @ref HandleFlag::DestroyOnDestruction + * specified. + * @see @fn_vk_keyword{DestroyInstance}, @ref release() + */ + ~Instance(); + + /** @brief Copying is not allowed */ + Instance& operator=(const Instance&) = delete; + + /** @brief Move assignment */ + Instance& operator=(Instance&& other) noexcept; + + /** @brief Underlying @type_vk{Instance} handle */ + VkInstance handle() { return _handle; } + /** @overload */ + operator VkInstance() { return _handle; } + + /** @brief Handle flags */ + HandleFlags handleFlags() const { return _flags; } + + /** @brief Instance version */ + Version version() const { return _version; } + + /** @brief Whether given version is supported on the instance */ + bool isVersionSupported(Version version) const { + return _version >= version; + } + + /** + * @brief Whether given extension is enabled + * + * Accepts instance 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 Instance-isExtensionEnabled + * + * Note that this returns @cpp true @ce only if given extension is + * supported by the driver *and* it was enabled in + * @ref InstanceCreateInfo when creating the @ref Instance. For + * querying extension support before creating an instance use + * @ref InstanceExtensionProperties::isSupported(). + */ + template bool isExtensionEnabled() const { + static_assert(Implementation::IsInstanceExtension::value, "expected a Vulkan instance extension"); + return _extensionStatus[E::InstanceIndex]; + } + + /** @overload */ + bool isExtensionEnabled(const InstanceExtension& extension) const; + + /** + * @brief Instance-specific Vulkan function pointers + * + * Function pointers are implicitly stored per-instance, use + * @ref populateGlobalFunctionPointers() to populate the global `vk*` + * functions. + */ + const FlextVkInstance& operator*() const { return _functionPointers; } + /** @overload */ + const FlextVkInstance* operator->() const { return &_functionPointers; } + + /** + * @brief Release the underlying Vulkan instance + * + * Releases ownership of the Vulkan instance and returns its handle so + * @fn_vk{DestroyInstance} is not called on destruction. The internal + * state is then equivalent to moved-from state. + * @see @ref wrap() + */ + VkInstance release(); + + /** + * @brief Populate global instance-level function pointers to be used with third-party code + * + * Populates instance-level global function pointers so third-party + * code is able to call global instance-level `vk*` functions: + * + * @snippet MagnumVk.cpp Instance-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 instance as the one returned by + * @ref handle(). + */ + void populateGlobalFunctionPointers(); + + #ifdef DOXYGEN_GENERATING_OUTPUT + private: + #endif + Implementation::InstanceState& state() { return *_state; } + + private: + template MAGNUM_VK_LOCAL void initializeExtensions(Containers::ArrayView enabledExtensions); + MAGNUM_VK_LOCAL void initialize(Version version, Int argc, const char** argv); + + VkInstance _handle; + HandleFlags _flags; + Version _version; + Math::BoolVector _extensionStatus; + Containers::Pointer _state; + + /* This member is bigger than you might think */ + FlextVkInstance _functionPointers; +}; + }} #endif diff --git a/src/Magnum/Vk/Test/CMakeLists.txt b/src/Magnum/Vk/Test/CMakeLists.txt index c41e2585e..d49d3b474 100644 --- a/src/Magnum/Vk/Test/CMakeLists.txt +++ b/src/Magnum/Vk/Test/CMakeLists.txt @@ -28,6 +28,7 @@ corrade_add_test(VkEnumsTest EnumsTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkExtensionsTest ExtensionsTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkExtensionPropertiesTest ExtensionPropertiesTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkHandleTest HandleTest.cpp LIBRARIES MagnumVk) +corrade_add_test(VkInstanceTest InstanceTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkIntegrationTest IntegrationTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkLayerPropertiesTest LayerPropertiesTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkResultTest ResultTest.cpp LIBRARIES MagnumVk) @@ -36,5 +37,6 @@ corrade_add_test(VkVersionTest VersionTest.cpp LIBRARIES MagnumVk) if(BUILD_VK_TESTS) corrade_add_test(VkExtensionPropertiesVkTest ExtensionPropertiesVkTest.cpp LIBRARIES MagnumVkTestLib) corrade_add_test(VkLayerPropertiesVkTest LayerPropertiesVkTest.cpp LIBRARIES MagnumVkTestLib) + corrade_add_test(VkInstanceVkTest InstanceVkTest.cpp LIBRARIES MagnumVk) corrade_add_test(VkVersionVkTest VersionVkTest.cpp LIBRARIES MagnumVk) endif() diff --git a/src/Magnum/Vk/Test/InstanceTest.cpp b/src/Magnum/Vk/Test/InstanceTest.cpp new file mode 100644 index 000000000..d42be6ffa --- /dev/null +++ b/src/Magnum/Vk/Test/InstanceTest.cpp @@ -0,0 +1,93 @@ +/* + 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/Instance.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct InstanceTest: TestSuite::Tester { + explicit InstanceTest(); + + void createInfoConstructNoInit(); + void createInfoConstructFromVk(); + + void constructNoCreate(); + void constructCopy(); +}; + +InstanceTest::InstanceTest() { + addTests({&InstanceTest::createInfoConstructNoInit, + &InstanceTest::createInfoConstructFromVk, + + &InstanceTest::constructNoCreate, + &InstanceTest::constructCopy}); +} + +void InstanceTest::createInfoConstructNoInit() { + InstanceCreateInfo info; + info->sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + new(&info) InstanceCreateInfo{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 InstanceTest::createInfoConstructFromVk() { + VkInstanceCreateInfo vkInfo; + vkInfo.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2; + + InstanceCreateInfo info{vkInfo}; + CORRADE_COMPARE(info->sType, VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void InstanceTest::constructNoCreate() { + { + Instance instance{NoCreate}; + CORRADE_VERIFY(!instance.handle()); + /* Instance function pointers should be null */ + CORRADE_VERIFY(!instance->CreateDevice); + } + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void InstanceTest::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::InstanceTest) diff --git a/src/Magnum/Vk/Test/InstanceVkTest.cpp b/src/Magnum/Vk/Test/InstanceVkTest.cpp new file mode 100644 index 000000000..672b35829 --- /dev/null +++ b/src/Magnum/Vk/Test/InstanceVkTest.cpp @@ -0,0 +1,508 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include + +#include "Magnum/Vk/Extensions.h" +#include "Magnum/Vk/ExtensionProperties.h" +#include "Magnum/Vk/Handle.h" +#include "Magnum/Vk/LayerProperties.h" +#include "Magnum/Vk/Instance.h" +#include "Magnum/Vk/Result.h" +#include "Magnum/Vk/Version.h" + +#include "MagnumExternal/Vulkan/flextVkGlobal.h" + +namespace Magnum { namespace Vk { namespace Test { namespace { + +struct InstanceVkTest: TestSuite::Tester { + explicit InstanceVkTest(); + + void createInfoConstructDefault(); + void createInfoApplicationInfo(); + void createInfoLayers(); + void createInfoExtensions(); + void createInfoCopiedStrings(); + + void construct(); + void constructLayerExtension(); + void constructCommandLineDisable(); + void constructCommandLineEnable(); + void constructMove(); + void constructUnknownLayer(); + void constructUnknownExtension(); + void wrap(); + void populateGlobalFunctionPointers(); +}; + +struct { + const char* nameDisable; + const char* nameEnable; + Containers::Array argsDisable, argsEnable; + bool driverVersionSupported, debugReportEnabled, validationFeaturesEnabled; + const char* log; +} ConstructCommandLineData[] { + /* Shouldn't print anything about version, enabled layers/exts if quiet + output is enabled. */ + {"quiet", "quiet, enabled layer + both extensions", + Containers::array({"", "--magnum-log", "quiet"}), + Containers::array({"", "--magnum-log", "quiet", + "--magnum-enable-layers", "VK_LAYER_KHRONOS_validation", + "--magnum-enable-instance-extensions", "VK_EXT_debug_report VK_EXT_validation_features"}), + true, true, true, + ""}, + {"", "enabled layer + both extensions", nullptr, + Containers::array({"", + "--magnum-enable-layers", "VK_LAYER_KHRONOS_validation", + "--magnum-enable-instance-extensions", "VK_EXT_debug_report VK_EXT_validation_features"}), + true, true, true, + "Instance version: Vulkan {}.{}{}\n" + "Enabled layers:\n" + " VK_LAYER_KHRONOS_validation\n" + "Enabled instance extensions:\n" + " VK_EXT_debug_report\n" + " VK_EXT_validation_features\n"}, + {"forced invalid version", "forced invalid version, enabled layer + both extensions", + Containers::array({"", "--magnum-vulkan-version", "eh"}), + Containers::array({"", "--magnum-vulkan-version", "eh", + "--magnum-enable-layers", "VK_LAYER_KHRONOS_validation", + "--magnum-enable-instance-extensions", "VK_EXT_debug_report VK_EXT_validation_features"}), + true, true, true, + "Invalid --magnum-vulkan-version eh, ignoring\n" + "Instance version: Vulkan {}.{}{}\n" + "Enabled layers:\n" + " VK_LAYER_KHRONOS_validation\n" + "Enabled instance extensions:\n" + " VK_EXT_debug_report\n" + " VK_EXT_validation_features\n"}, + {"forced version", "forced version, enabled layer + both extensions", + Containers::array({"", "--magnum-vulkan-version", "1.0"}), + Containers::array({"", "--magnum-vulkan-version", "1.0", + "--magnum-enable-layers", "VK_LAYER_KHRONOS_validation", + "--magnum-enable-instance-extensions", "VK_EXT_debug_report VK_EXT_validation_features"}), + false, true, true, + "Instance version: Vulkan 1.0\n" + "Enabled layers:\n" + " VK_LAYER_KHRONOS_validation\n" + "Enabled instance extensions:\n" + " VK_EXT_debug_report\n" + " VK_EXT_validation_features\n"}, + {"disabled layer + layer-only extension", "enabled extension", + Containers::array({"", + "--magnum-disable-layers", "VK_LAYER_KHRONOS_validation", + "--magnum-disable-extensions", "VK_EXT_validation_features"}), + Containers::array({"", + "--magnum-enable-instance-extensions", "VK_EXT_debug_report"}), + true, true, false, + "Instance version: Vulkan {}.{}{}\n" + "Enabled instance extensions:\n" + " VK_EXT_debug_report\n"}, + {"disabled extension", "enabled layer + one extension", + Containers::array({"", + "--magnum-disable-extensions", "VK_EXT_debug_report"}), + Containers::array({"", + "--magnum-enable-layers", "VK_LAYER_KHRONOS_validation", + "--magnum-enable-instance-extensions", "VK_EXT_validation_features"}), + true, false, true, + "Instance version: Vulkan {}.{}{}\n" + "Enabled layers:\n" + " VK_LAYER_KHRONOS_validation\n" + "Enabled instance extensions:\n" + " VK_EXT_validation_features\n"}, + {"disabled extensions + layer", "", + Containers::array({"", + "--magnum-disable-layers", "VK_LAYER_KHRONOS_validation", + "--magnum-disable-extensions", "VK_EXT_debug_report VK_EXT_validation_features"}), + nullptr, + true, false, false, + "Instance version: Vulkan {}.{}{}\n"}, +}; + +InstanceVkTest::InstanceVkTest() { + addTests({&InstanceVkTest::createInfoConstructDefault, + &InstanceVkTest::createInfoApplicationInfo, + &InstanceVkTest::createInfoLayers, + &InstanceVkTest::createInfoExtensions, + &InstanceVkTest::createInfoCopiedStrings, + + &InstanceVkTest::construct, + &InstanceVkTest::constructLayerExtension}); + + addInstancedTests({&InstanceVkTest::constructCommandLineDisable, + &InstanceVkTest::constructCommandLineEnable}, + Containers::arraySize(ConstructCommandLineData)); + + addTests({&InstanceVkTest::constructMove, + &InstanceVkTest::constructUnknownLayer, + &InstanceVkTest::constructUnknownExtension, + &InstanceVkTest::wrap, + &InstanceVkTest::populateGlobalFunctionPointers}); +} + +using namespace Containers::Literals; + +void InstanceVkTest::createInfoConstructDefault() { + InstanceCreateInfo info; + CORRADE_VERIFY(info->sType); + CORRADE_VERIFY(!info->pNext); + CORRADE_VERIFY(!info->ppEnabledLayerNames); + CORRADE_COMPARE(info->enabledLayerCount, 0); + CORRADE_VERIFY(!info->ppEnabledExtensionNames); + CORRADE_COMPARE(info->enabledExtensionCount, 0); + + CORRADE_VERIFY(info->pApplicationInfo); + CORRADE_COMPARE(Version(info->pApplicationInfo->apiVersion), enumerateInstanceVersion()); + CORRADE_COMPARE(info->pApplicationInfo->applicationVersion, 0); + CORRADE_COMPARE(info->pApplicationInfo->engineVersion, 0); + CORRADE_COMPARE(info->pApplicationInfo->pEngineName, "Magnum"_s); +} + +void InstanceVkTest::createInfoApplicationInfo() { + Containers::StringView name = "Magnum::Vk::Test::InstanceVkTest"_s; + + InstanceCreateInfo info; + CORRADE_VERIFY(info->pApplicationInfo); + CORRADE_VERIFY(!info->pApplicationInfo->pApplicationName); + CORRADE_COMPARE(Version(info->pApplicationInfo->applicationVersion), Version{}); + + /* Setting an empty name should do nothing */ + info.setApplicationInfo({}, {}); + CORRADE_VERIFY(!info->pApplicationInfo->pApplicationName); + CORRADE_COMPARE(Version(info->pApplicationInfo->applicationVersion), Version{}); + + info.setApplicationInfo(name, version(0, 0, 1)); + /* The pointer should be to the global data */ + CORRADE_COMPARE(static_cast(info->pApplicationInfo->pApplicationName), name.data()); + CORRADE_COMPARE(Version(info->pApplicationInfo->applicationVersion), version(0, 0, 1)); + + /* Setting an empty view should put nullptr back */ + info.setApplicationInfo({}, {}); + CORRADE_VERIFY(!info->pApplicationInfo->pApplicationName); + CORRADE_COMPARE(Version(info->pApplicationInfo->applicationVersion), Version{}); +} + +void InstanceVkTest::createInfoLayers() { + Containers::StringView layer = "VK_LAYER_KHRONOS_validation"_s; + Containers::StringView another = "VK_LAYER_this_doesnt_exist"_s; + + InstanceCreateInfo info; + CORRADE_VERIFY(!info->ppEnabledLayerNames); + CORRADE_COMPARE(info->enabledLayerCount, 0); + + info.addEnabledLayers({layer}); + CORRADE_VERIFY(info->ppEnabledLayerNames); + CORRADE_COMPARE(info->enabledLayerCount, 1); + /* The pointer should be to the global data */ + CORRADE_COMPARE(static_cast(info->ppEnabledLayerNames[0]), layer.data()); + + info.addEnabledLayers({another, layer}); + CORRADE_COMPARE(info->enabledLayerCount, 3); + /* The pointer should be to the global data */ + CORRADE_COMPARE(static_cast(info->ppEnabledLayerNames[0]), layer.data()); + CORRADE_COMPARE(static_cast(info->ppEnabledLayerNames[1]), another.data()); + CORRADE_COMPARE(static_cast(info->ppEnabledLayerNames[2]), layer.data()); +} + +void InstanceVkTest::createInfoExtensions() { + InstanceCreateInfo info; + 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::external_fence_capabilities::string().data()); + + info.addEnabledExtensions( + {Extensions::KHR::external_semaphore_capabilities{}, + Extensions::KHR::get_physical_device_properties2{}}); + CORRADE_COMPARE(info->enabledExtensionCount, 3); + /* The pointer should be to the global data */ + CORRADE_COMPARE(static_cast(info->ppEnabledExtensionNames[0]), + Extensions::KHR::external_fence_capabilities::string().data()); + CORRADE_COMPARE(static_cast(info->ppEnabledExtensionNames[1]), + Extensions::KHR::external_semaphore_capabilities::string().data()); + CORRADE_COMPARE(static_cast(info->ppEnabledExtensionNames[2]), + Extensions::KHR::get_physical_device_properties2::string().data()); +} + +void InstanceVkTest::createInfoCopiedStrings() { + Containers::StringView globalButNotNullTerminated = "VK_LAYER_KHRONOS_validation3"_s.except(1); + Containers::String localButNullTerminated = Extensions::KHR::external_memory_capabilities::string(); + + InstanceCreateInfo info; + info.setApplicationInfo(localButNullTerminated, {}) + .addEnabledLayers({globalButNotNullTerminated}) + .addEnabledExtensions({localButNullTerminated}); + CORRADE_COMPARE(info->enabledLayerCount, 1); + CORRADE_COMPARE(info->enabledExtensionCount, 1); + + CORRADE_COMPARE(info->pApplicationInfo->pApplicationName, localButNullTerminated); + CORRADE_VERIFY(info->pApplicationInfo->pApplicationName != localButNullTerminated.data()); + + CORRADE_COMPARE(info->ppEnabledLayerNames[0], globalButNotNullTerminated); + CORRADE_VERIFY(info->ppEnabledLayerNames[0] != globalButNotNullTerminated.data()); + + CORRADE_COMPARE(info->ppEnabledExtensionNames[0], localButNullTerminated); + CORRADE_VERIFY(info->ppEnabledExtensionNames[0] != localButNullTerminated.data()); +} + +void InstanceVkTest::construct() { + { + Instance instance; + CORRADE_VERIFY(instance.handle()); + /* Instance function pointers should be populated */ + CORRADE_VERIFY(instance->CreateDevice); + CORRADE_COMPARE(instance.handleFlags(), HandleFlag::DestroyOnDestruction); + CORRADE_COMPARE(instance.version(), enumerateInstanceVersion()); + /* Instance version is supported */ + CORRADE_VERIFY(instance.isVersionSupported(enumerateInstanceVersion())); + CORRADE_VERIFY(!instance.isVersionSupported(Version::None)); + /* No extensions are enabled by default ... */ + CORRADE_VERIFY(!instance.isExtensionEnabled()); + /* ... and thus also no function pointers loaded */ + CORRADE_VERIFY(!instance->CreateDebugReportCallbackEXT); + } + + /* Shouldn't crash or anything */ + CORRADE_VERIFY(true); +} + +void InstanceVkTest::constructLayerExtension() { + if(!enumerateLayerProperties().isSupported("VK_LAYER_KHRONOS_validation")) + CORRADE_SKIP("VK_LAYER_KHRONOS_validation not supported, can't test"); + if(!enumerateInstanceExtensionProperties({"VK_LAYER_KHRONOS_validation"}).isSupported()) + CORRADE_SKIP("VK_EXT_debug_report not supported, can't test"); + + Instance instance{InstanceCreateInfo{} + .setApplicationInfo("InstanceVkTest", version(0, 0, 1)) + .addEnabledLayers({"VK_LAYER_KHRONOS_validation"_s}) + .addEnabledExtensions({ + Extensions::EXT::debug_report::string(), + "VK_EXT_validation_features"_s + })}; + CORRADE_VERIFY(instance.handle()); + + /* Extensions should be reported as enabled ... */ + CORRADE_VERIFY(instance.isExtensionEnabled()); + CORRADE_VERIFY(instance.isExtensionEnabled(Extensions::EXT::validation_features{})); + /* ... and function pointers loaded */ + CORRADE_VERIFY(instance->CreateDebugReportCallbackEXT); + /* no entrypoints to test for EXT_validation_features */ +} + +void InstanceVkTest::constructCommandLineDisable() { + auto&& data = ConstructCommandLineData[testCaseInstanceId()]; + setTestCaseDescription(data.nameDisable); + + if(!enumerateLayerProperties().isSupported("VK_LAYER_KHRONOS_validation")) + CORRADE_SKIP("VK_LAYER_KHRONOS_validation not supported, can't test"); + if(!enumerateInstanceExtensionProperties({"VK_LAYER_KHRONOS_validation"}).isSupported()) + CORRADE_SKIP("VK_EXT_validation_features not supported, can't test"); + + std::ostringstream out; + Warning redirectWarning{&out}; + Debug redirectOutput{&out}; + Instance instance{InstanceCreateInfo{Int(data.argsDisable.size()), data.argsDisable} + .setApplicationInfo("InstanceVkTest", version(0, 0, 1)) + .addEnabledLayers({"VK_LAYER_KHRONOS_validation"_s}) + .addEnabledExtensions()}; + CORRADE_VERIFY(instance.handle()); + CORRADE_COMPARE(instance.isVersionSupported(enumerateInstanceVersion()), data.driverVersionSupported); + CORRADE_COMPARE(instance.isExtensionEnabled(), data.debugReportEnabled); + CORRADE_COMPARE(instance.isExtensionEnabled(), data.validationFeaturesEnabled); + + /** @todo cleanup when Debug::toString() or some similar utility exists */ + UnsignedInt major = versionMajor(enumerateInstanceVersion()); + UnsignedInt minor = versionMinor(enumerateInstanceVersion()); + UnsignedInt patch = versionPatch(enumerateInstanceVersion()); + /* Vulkan 1.0 instances report 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(!!instance->CreateDebugReportCallbackEXT, data.debugReportEnabled); +} + +void InstanceVkTest::constructCommandLineEnable() { + auto&& data = ConstructCommandLineData[testCaseInstanceId()]; + setTestCaseDescription(data.nameEnable); + + if(!enumerateLayerProperties().isSupported("VK_LAYER_KHRONOS_validation")) + CORRADE_SKIP("VK_LAYER_KHRONOS_validation not supported, can't test"); + if(!enumerateInstanceExtensionProperties({"VK_LAYER_KHRONOS_validation"}).isSupported()) + CORRADE_SKIP("VK_EXT_validation_features not supported, can't test"); + + std::ostringstream out; + Warning redirectWarning{&out}; + Debug redirectOutput{&out}; + Instance instance{InstanceCreateInfo{Int(data.argsEnable.size()), data.argsEnable} + /* Nothing enabled by the application */ + }; + CORRADE_VERIFY(instance.handle()); + CORRADE_COMPARE(instance.isVersionSupported(enumerateInstanceVersion()), data.driverVersionSupported); + CORRADE_COMPARE(instance.isExtensionEnabled(), data.debugReportEnabled); + CORRADE_COMPARE(instance.isExtensionEnabled(), data.validationFeaturesEnabled); + + /** @todo cleanup when Debug::toString() or some similar utility exists */ + UnsignedInt major = versionMajor(enumerateInstanceVersion()); + UnsignedInt minor = versionMinor(enumerateInstanceVersion()); + UnsignedInt patch = versionPatch(enumerateInstanceVersion()); + /* Vulkan 1.0 instances report 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(!!instance->CreateDebugReportCallbackEXT, data.debugReportEnabled); +} + +void InstanceVkTest::constructMove() { + InstanceExtensionProperties extensions = enumerateInstanceExtensionProperties(); + if(!extensions.isSupported()) + CORRADE_SKIP("VK_KHR_get_physical_device_properties2 not supported, can't test"); + + Instance a{InstanceCreateInfo{} + .setApplicationInfo("InstanceVkTest", version(0, 0, 1)) + .addEnabledExtensions()}; + VkInstance handle = a.handle(); + Version version = a.version(); + CORRADE_VERIFY(handle); + CORRADE_VERIFY(version != Version{}); + + Instance 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->CreateDevice); + + Instance 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->CreateDevice); + CORRADE_VERIFY(c->CreateDevice); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void InstanceVkTest::constructUnknownLayer() { + CORRADE_SKIP("Currently this hits an internal assert, which can't be tested."); + + std::ostringstream out; + Error redirectError{&out}; + Instance instance{InstanceCreateInfo{} + .addEnabledLayers({"VK_LAYER_this_doesnt_exist"_s})}; + CORRADE_COMPARE(out.str(), "TODO"); +} + +void InstanceVkTest::constructUnknownExtension() { + CORRADE_SKIP("Currently this hits an internal assert, which can't be tested."); + + std::ostringstream out; + Error redirectError{&out}; + Instance instance{InstanceCreateInfo{} + .addEnabledExtensions({"VK_this_doesnt_exist"_s})}; + CORRADE_COMPARE(out.str(), "TODO"); +} + +void InstanceVkTest::wrap() { + InstanceExtensionProperties properties = enumerateInstanceExtensionProperties(); + if(!properties.isSupported()) + CORRADE_SKIP("VK_EXT_debug_report not supported, can't test"); + if(!properties.isSupported()) + CORRADE_SKIP("VK_KHR_get_physical_device_properties2 not supported, can't test"); + + InstanceCreateInfo info; + info.addEnabledExtensions< + Extensions::EXT::debug_report, + Extensions::KHR::get_physical_device_properties2>(); + + VkInstance instance; + CORRADE_COMPARE(Result(vkCreateInstance(info, nullptr, &instance)), Result::Success); + CORRADE_VERIFY(instance); + + { + /* Wrapping should load the basic function pointers */ + auto wrapped = Instance::wrap(instance, Version::Vk11, { + Extensions::EXT::debug_report::string() + }, HandleFlag::DestroyOnDestruction); + CORRADE_VERIFY(wrapped->DestroyInstance); + + /* 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->CreateDebugReportCallbackEXT); + + /* 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->GetPhysicalDeviceProperties2KHR); + + /* Releasing won't destroy anything ... */ + CORRADE_COMPARE(wrapped.release(), instance); + } + + /* ...so we can wrap it again, non-owned, and then destroy it manually */ + auto wrapped = Instance::wrap(instance, Version::Vk10, {}); + CORRADE_VERIFY(wrapped->DestroyInstance); + wrapped->DestroyInstance(instance, nullptr); +} + +void InstanceVkTest::populateGlobalFunctionPointers() { + vkDestroyInstance = nullptr; + + Instance instance; + CORRADE_VERIFY(!vkDestroyInstance); + instance.populateGlobalFunctionPointers(); + CORRADE_VERIFY(vkDestroyInstance); + CORRADE_VERIFY(vkDestroyInstance == instance->DestroyInstance); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Vk::Test::InstanceVkTest) diff --git a/src/Magnum/Vk/Vk.h b/src/Magnum/Vk/Vk.h index 023968525..587ccef7a 100644 --- a/src/Magnum/Vk/Vk.h +++ b/src/Magnum/Vk/Vk.h @@ -38,6 +38,8 @@ class Extension; class ExtensionProperties; enum class HandleFlag: UnsignedByte; typedef Containers::EnumSet HandleFlags; +class Instance; +class InstanceCreateInfo; class InstanceExtension; class InstanceExtensionProperties; class LayerProperties;