new in Git master
Vulkan wrapping layerOverview of the base Vulkan wrapper API.
The Magnum::
CreateInfo structure wrappers
The *CreateInfo
/ *AllocateInfo
structures tend to be rather complex compared to the created object and because they're only needed once when constructing the object, their definition is put in a separate header. This should help compile times in larger codebases where Vulkan objects are constructed in one place and then passed around to functions, without needing to know anything about the CreateInfo
-related definitions anymore. Then, for convenience, each ThingCreateInfo.h
header is guaranteed to include the Thing.h
as well, so if you want both you can do for example just
#include <Magnum/Vk/RenderPassCreateInfo.h>
instead of having to verbosely include both:
#include <Magnum/Vk/RenderPass.h> #include <Magnum/Vk/RenderPassCreateInfo.h>
Unless said otherwise in the particular constructor docs, a Vk::*CreateInfo
instance has all required fields set to valid values upon construction, with everything else optional. One exception is for example Vk::
To completely mitigate the overhead from instantiating wrapper *CreateInfo
classes, each of them can also be constructed using the NoInit tag, which will skip all initialization and leave the contents unspecified to be filled later. In case the structure contains some heap-allocated state, the NoInit constructor is guaranteed to not allocate. Note that with NoInit constructors you have the full responsibility to correctly set up all members.
Instance and device wrappers
Compared to OpenGL, which has a concept of "current context", Vulkan doesn't have any implicit globals. In the Vk library, each object contains a reference to a corresponding instance or device. This was chosen as a reasonable tradeoff between requiring an explicit instance/device parameter in each API (which would be too error-prone and annoying to use) and having an implicit thread-local instance/device (which would repeat the well-known pain points of OpenGL).
Vulkan API entrypoints aren't global either because each instance and device can have a different set of enabled layers and extensions, and thus different instance- and device-local function pointers. While the Vulkan specification allows device-level functions to be queried on an instance and thus use the same function pointers on a variety of devices, such workflow implies additional dispatch overhead, and thus isn't recommended. Magnum instead stores instance- and device-level function pointers locally in each Vk::
Vk::Instance instance{…}; VkPhysicalDeviceGroupPropertiesKHR properties[10]; UnsignedInt count = Containers::arraySize(properties); instance->EnumeratePhysicalDeviceGroupsKHR(instance, &count, properties);
For convenience and for easier interaction with 3rd party code, such pointers can be made global by calling Vk::vk*
functions as usual. However, all implications coming from these being tied to a particular instance/device still apply:
#include <MagnumExternal/Vulkan/flextVkGlobal.h> … instance.populateGlobalFunctionPointers(); VkPhysicalDeviceGroupPropertiesKHR properties[10]; UnsignedInt count = Containers::arraySize(properties); vkEnumeratePhysicalDeviceGroupsKHR(instance, &count, properties);
Host memory allocation
As opposed to device memory allocation, which is exposed through Vk::nullptr
in any vkCreate*()
calls. If you want to supply your own callbacks, you have to call these functions directly — ideally through the instance- and device-level function pointers available through Vk::
Common interfaces for interaction with raw Vulkan code
Each wrapped Vulkan object has a handle() getter, giving back the underlying Vulkan handle such as VkInstance. In addition it's also implicitly convertible to that handle type, which means you can pass it as-is to raw Vulkan APIs. You can also use release() to release its ownership and continue to use it as a regular handle. Conversely, any Vulkan handle can be wrapped into a first-class Magnum object using a corresponding wrap() function.
Similarly, all Vk::Vk*CreateInfo
pointer in order to be easily passable directly to Vulkan APIs. You can create them from an existing Vk*CreateInfo
instances as well, and use operator->() to access the wrapped structure to supply additional parameters not exposed by Magnum. However take care to not clash with values and pointers already set:
Vk::InstanceCreateInfo info{…}; /* Add a custom validation features setup */ VkValidationFeaturesEXT validationFeatures{}; validationFeatures.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT; validationFeatures.enabledValidationFeatureCount = 1; constexpr auto bestPractices = VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT; validationFeatures.pEnabledValidationFeatures = &bestPractices; CORRADE_INTERNAL_ASSERT(!info->pNext); // or find the end of the pNext chain info->pNext = &validationFeatures;
Similarly to the NoInit constructors, constructing a Vk::*CreateInfo
from the underlying Vulkan structure is guaranteed to not allocate as well — only a shallow copy of the top-level structure is made and internal pointers, if any, keep pointing to the originating data. That approach has some implications:
- The user is responsible for ensuring those stay in scope for as long as the structure is needed
- When calling Magnum's own APIs on the instance that was constructed from a raw Vulkan structure, the pointed-to-data aren't touched in any way (which means they can be
const
), however they might get abandoned and the top-level structure pointed elsewhere. - Existing
pNext
chains are preserved. Calling Magnum's own APIs may result in new entries added to the chain, but always at the front (again in order to allow the pointed-to-data beconst
). It's a responsibility of the user to ensure no conflicting or duplicate entries are present in the original chain when mixing it with Magnum's APIs.
It's also possible to add new structures to the pNext
chain of an existing instance. However, to prevent conflicts with Magnum which inserts at the front, new raw structures should be always appended at the end of the chain.
Optimizing instance and device property retrieval
Retrieving layer and extension info as well as device properties involves allocations, string operations, sorting and other potentially expensive work both on the application side and in the driver. While the Vk::
For Vk::
Vk::LayerProperties layers = …; Vk::InstanceExtensionProperties extensions = …; /* Pass the layer and extension properties for use by InstanceCreateInfo */ Vk::InstanceCreateInfo info{argc, argv, &layers, &extensions}; if(layers.isSupported("VK_LAYER_KHRONOS_validation"_s)) info.addEnabledLayers({"VK_LAYER_KHRONOS_validation"_s}); if(extensions.isSupported<Vk::Extensions::EXT::debug_report>()) info.addEnabledExtensions<Vk::Extensions::EXT::debug_report>(); … Vk::Instance instance{info};
For Vk::
Vk::Device device{instance, Vk::DeviceCreateInfo{Vk::pickDevice(instance)} .addQueues(…) … };
However, if you instantiate Vk::
Vk::DeviceProperties properties = Vk::pickDevice(instance); Vk::ExtensionProperties extensions = properties.enumerateExtensionProperties(); /* Move the device properties to the info structure, pass extension properties to allow reuse as well */ Vk::DeviceCreateInfo info{std::move(properties), &extensions}; if(extensions.isSupported<Vk::Extensions::EXT::index_type_uint8>()) info.addEnabledExtensions<Vk::Extensions::EXT::index_type_uint8>(); if(extensions.isSupported("VK_NV_mesh_shader"_s)) info.addEnabledExtensions({"VK_NV_mesh_shader"_s}); … /* Finally, be sure to move the info structure to the device as well */ Vk::Device device{instance, std::move(info)};
Important differences in naming
- To emphasise the distinction between rasterization and raytracing pipelines and prevent confusion, Vk::
RasterizationPipelineCreateInfo is used for VkGraphicsPipelineCreateInfo, and similarly for related APIs, such as Vk:: DynamicRasterizationState containing a rasterization-related subset of VkDynamicState or Vk:: PipelineStage:: AllRasterization for VK_ PIPELINE_ STAGE_ ALL_ GRAPHICS_ BIT. - Because not all VkFormat values can be used as both pixel and vertex formats (for example there can be double-precision vertices, but not pixels, or it makes no sense to use ASTC for vertices), the enum is split into Vk::
PixelFormat and Vk:: VertexFormat. For pixel formats the naming is simplified for easier typing (Vk:: PixelFormat:: RGBA8Srgb instead of VK_ FORMAT_ R8G8B8A8_ SRGB, for example); for vertex formats the RGBA notion is omitted and replaced with just component count, in most cases matching Magnum scalar and vector type names (Vk:: VertexFormat:: Vector3 instead of VK_ FORMAT_ R32G32B32_ SFLOAT) - As Magnum has several mesh-related abstraction (and Vulkan none), Vk::
MeshPrimitive is used instead of VkPrimitiveTopology for a better visual grouping of related concepts